sinbotra 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 978fcbcaca2a3766c1a7b655713a30cdff701d70
|
4
|
+
data.tar.gz: 58df78433260eb468f01632274f292c947955f62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e5aaeaf20e279e5c4f47063ba10267cb0ed98955608c1af215a0f16768eba80a25f200793887eb8b0f1b0fc6a6a07ab391d1f2e0e16bfa7c703ab7014cce767
|
7
|
+
data.tar.gz: 3a5202cc1d82c29e0258b570644b27d7a9d1d3e20451d50cac4b020c985ecd3c6b32be9ab382f817feab2cf5c805aedd01abe4362e0e49cbd29cb9e9e9718a18
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -39,3 +39,129 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
|
|
39
39
|
|
40
40
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
41
|
|
42
|
+
## New API!
|
43
|
+
|
44
|
+
You take an incoming message
|
45
|
+
|
46
|
+
Create a user out of the sender and a bot out of the user
|
47
|
+
|
48
|
+
You tell the bot to handle the message
|
49
|
+
|
50
|
+
It checks if the user is in a conversation
|
51
|
+
|
52
|
+
If YES then handle the current conversation step. On step end, increase conversation step or end convo
|
53
|
+
|
54
|
+
If NO then cycle through handlers
|
55
|
+
|
56
|
+
If still nothing, answer with default if it exists
|
57
|
+
|
58
|
+
If still not, use default I don't understandj
|
59
|
+
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class BurpeeBot < Sinbotra::Bot
|
63
|
+
listeners onboard: /hi|hello/,
|
64
|
+
start_workout: /start|begin/
|
65
|
+
|
66
|
+
default_listener :default
|
67
|
+
|
68
|
+
conversations onboarding: OnboardingConversation,
|
69
|
+
start_workout: StartWorkoutConversation
|
70
|
+
|
71
|
+
actions start_workout: StartWorkoutAction
|
72
|
+
|
73
|
+
def onboard(msg, user)
|
74
|
+
bot.say(user, "Hello #{user.name}")
|
75
|
+
start_conversation(:onboarding)
|
76
|
+
end
|
77
|
+
|
78
|
+
def start_workout(msg, user)
|
79
|
+
end
|
80
|
+
|
81
|
+
def default(msg, user)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
BurpeeBot.perform_action(:start_workout, args)
|
86
|
+
|
87
|
+
class StartWorkoutAction
|
88
|
+
def call(bot, args={})
|
89
|
+
athletes = Athlete::Repo.find_fresh
|
90
|
+
athletes.each do |athlete|
|
91
|
+
bot.send_text(athlete.fb_id, "Time to work out!!!")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Sinbotra::Conversation
|
97
|
+
class << self
|
98
|
+
attr_reader :step_names
|
99
|
+
def steps(*all_steps)
|
100
|
+
@step_names = all_steps
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
attr_reader :current_user
|
105
|
+
attr_reader :step_num
|
106
|
+
|
107
|
+
def initialize(user, bot)
|
108
|
+
@current_user = user
|
109
|
+
@step_num = 0
|
110
|
+
@active = true
|
111
|
+
@steps = self.class.step_names
|
112
|
+
end
|
113
|
+
|
114
|
+
def next_step!
|
115
|
+
@step_num += 1
|
116
|
+
if @step_num == @steps.size
|
117
|
+
end_conversation!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def say(txt)
|
122
|
+
STDOUT.puts(txt)
|
123
|
+
end
|
124
|
+
|
125
|
+
def end_conversation!
|
126
|
+
@step_num = -1
|
127
|
+
@active = false
|
128
|
+
end
|
129
|
+
|
130
|
+
def active?; @active; end
|
131
|
+
end
|
132
|
+
|
133
|
+
class OnboardingConversation
|
134
|
+
include Sinbotra::Conversation
|
135
|
+
|
136
|
+
steps :ask_name, :ask_age, :ask_fitness_level, :all_good
|
137
|
+
|
138
|
+
def ask_name(msg)
|
139
|
+
say("Hello, what is your name?")
|
140
|
+
# I have access to current_user
|
141
|
+
next_step!
|
142
|
+
end
|
143
|
+
|
144
|
+
def ask_age(msg, bot)
|
145
|
+
say("Great, your name is #{msg.text}")
|
146
|
+
say("How old are you?")
|
147
|
+
next_step!
|
148
|
+
end
|
149
|
+
|
150
|
+
def ask_fitness_level(msg, bot)
|
151
|
+
say("Great, your #{msg.text} years old.")
|
152
|
+
say("What's your fitness level?")
|
153
|
+
next_step!
|
154
|
+
end
|
155
|
+
|
156
|
+
def all_good(msg, bot)
|
157
|
+
say("Great, your fitness level is #{msg.text}.")
|
158
|
+
say("We have everything we need!")
|
159
|
+
end_conversation!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Bot::User
|
164
|
+
attr_reader :id, :current_converation
|
165
|
+
end
|
166
|
+
|
167
|
+
```
|
data/examples/.keep
ADDED
File without changes
|
data/lib/sinbotra.rb
CHANGED
@@ -6,7 +6,13 @@ require "sinbotra/config"
|
|
6
6
|
|
7
7
|
# Require sinbotra framework
|
8
8
|
require "sinbotra/version"
|
9
|
-
require "sinbotra/
|
9
|
+
require "sinbotra/message_store"
|
10
|
+
require "sinbotra/message_handler"
|
11
|
+
require "sinbotra/bot/redis_store"
|
12
|
+
require "sinbotra/bot/user"
|
13
|
+
require "sinbotra/bot/user_store"
|
14
|
+
require "sinbotra/bot/user_repo"
|
15
|
+
require "sinbotra/bot/conversation"
|
10
16
|
require "sinbotra/messenger"
|
11
17
|
|
12
18
|
module Sinbotra
|
data/lib/sinbotra/bot.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
require "sinbotra/bot/
|
2
|
-
require "sinbotra/bot/conversation_repo"
|
1
|
+
require "sinbotra/bot/redis_store"
|
3
2
|
require "sinbotra/bot/user"
|
3
|
+
require "sinbotra/bot/user_store"
|
4
4
|
require "sinbotra/bot/user_repo"
|
5
|
+
require "sinbotra/bot/conversation"
|
5
6
|
|
6
7
|
require "sucker_punch"
|
7
8
|
|
@@ -25,7 +26,9 @@ module Sinbotra
|
|
25
26
|
def receive(provider, msg)
|
26
27
|
handler = @handlers[provider.to_sym]
|
27
28
|
Sinbotra::Config.logger.debug("MESSAGE DEBUG:\n" + msg.inspect) if ENV["DEBUG"]
|
28
|
-
|
29
|
+
user = Sinbotra::User.from_message(msg)
|
30
|
+
bot = handler.new(user)
|
31
|
+
bot.handle_message(msg)
|
29
32
|
end
|
30
33
|
|
31
34
|
def handle_action(action_name, provider, handler, opts={})
|
@@ -35,10 +38,9 @@ module Sinbotra
|
|
35
38
|
|
36
39
|
include SuckerPunch::Job
|
37
40
|
|
38
|
-
def perform(provider,
|
39
|
-
|
40
|
-
|
41
|
-
Sinbotra::Bot.receive(provider, m)
|
41
|
+
def perform(provider, msgs)
|
42
|
+
msgs.each do |msg|
|
43
|
+
Sinbotra::Bot.receive(provider, msg)
|
42
44
|
end
|
43
45
|
end
|
44
46
|
end
|
@@ -1,86 +1,85 @@
|
|
1
1
|
class Sinbotra::Bot
|
2
2
|
class Conversation
|
3
|
-
|
3
|
+
class << self
|
4
|
+
attr_reader :_steps
|
4
5
|
|
5
|
-
|
6
|
-
attr_reader :steps
|
6
|
+
private
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@store = store
|
12
|
-
update_current_step(0)
|
13
|
-
store!(:is_active, false)
|
8
|
+
def steps(*_steps)
|
9
|
+
@_steps = _steps
|
10
|
+
end
|
14
11
|
end
|
15
12
|
|
16
|
-
def
|
17
|
-
|
13
|
+
def initialize(bot, platform)
|
14
|
+
@bot = bot
|
15
|
+
@current_user = bot.current_user
|
16
|
+
@message = bot.message
|
17
|
+
@platform = platform
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
20
|
+
def start
|
21
|
+
run_step!(current_user.conversation.step, message)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def continue_dialogue
|
25
|
+
run_step!(current_user.conversation.step, message)
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
@steps << Step.new(id, blk)
|
30
|
-
end
|
28
|
+
private
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
attr_reader :current_user
|
31
|
+
attr_reader :message
|
32
|
+
attr_reader :platform
|
35
33
|
|
36
|
-
def
|
37
|
-
@
|
34
|
+
def start_conversation(convo_id)
|
35
|
+
@bot.start_conversation(convo_id)
|
38
36
|
end
|
39
37
|
|
40
|
-
def
|
41
|
-
|
38
|
+
def has_conversation?(intent)
|
39
|
+
@bot.has_conversation(intent)
|
42
40
|
end
|
43
41
|
|
44
|
-
def
|
45
|
-
|
42
|
+
def get_intent(msg)
|
43
|
+
@bot.get_intent(msg)
|
46
44
|
end
|
47
45
|
|
48
|
-
def
|
49
|
-
new_step = current_step + 1
|
50
|
-
update_current_step(new_step)
|
51
|
-
done! if completed_last_step?
|
52
|
-
end
|
46
|
+
def steps; self.class._steps; end
|
53
47
|
|
54
|
-
def
|
55
|
-
next_step!
|
56
|
-
perform_current_step(bot)
|
57
|
-
end
|
48
|
+
def step(n) steps[n]; end
|
58
49
|
|
59
|
-
def
|
60
|
-
|
50
|
+
def step_count; steps.size; end
|
51
|
+
|
52
|
+
def run_step!(step_number, message)
|
53
|
+
send(step(step_number), message)
|
61
54
|
end
|
62
55
|
|
63
|
-
def
|
64
|
-
|
65
|
-
store!(:is_active, false)
|
56
|
+
def skip_to_next_step!
|
57
|
+
next_step!
|
66
58
|
end
|
67
59
|
|
68
|
-
def
|
69
|
-
|
60
|
+
def has_steps?
|
61
|
+
current_user.conversation.step < step_count - 1
|
70
62
|
end
|
71
63
|
|
72
|
-
|
64
|
+
def next_step!
|
65
|
+
has_steps? ? current_user.next_step! : done!
|
66
|
+
end
|
73
67
|
|
74
|
-
def
|
75
|
-
|
68
|
+
def next_step_now!
|
69
|
+
if has_steps?
|
70
|
+
current_user.next_step!
|
71
|
+
run_step!(current_user.conversation.step, message)
|
72
|
+
else
|
73
|
+
raise ArgumentError, "We're out of steps, shouldn't be skipping: #{self}"
|
74
|
+
end
|
76
75
|
end
|
77
76
|
|
78
|
-
def
|
79
|
-
|
77
|
+
def repeat_step!
|
78
|
+
# do nothing, leave state the same
|
80
79
|
end
|
81
80
|
|
82
|
-
def
|
83
|
-
|
81
|
+
def done!
|
82
|
+
current_user.end_conversation!
|
84
83
|
end
|
85
84
|
end
|
86
85
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Sinbotra::Bot
|
2
|
+
class RedisStore
|
3
|
+
def get(id)
|
4
|
+
res = $redis.get(make_key(id))
|
5
|
+
Marshal.load(res) unless res.nil?
|
6
|
+
end
|
7
|
+
alias_method :[], :get
|
8
|
+
|
9
|
+
def set(id, obj)
|
10
|
+
raw = Marshal.dump(obj)
|
11
|
+
$redis.set(make_key(id), raw)
|
12
|
+
end
|
13
|
+
alias_method :[]=, :set
|
14
|
+
|
15
|
+
def make_key(id)
|
16
|
+
raise NotImplementedError, "make_key needs to be implemented by children"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
data/lib/sinbotra/bot/user.rb
CHANGED
@@ -1,59 +1,22 @@
|
|
1
1
|
require "securerandom"
|
2
|
-
require "sinbotra/bot/default_message_history"
|
3
2
|
|
4
3
|
class Sinbotra::Bot
|
5
4
|
class User
|
6
|
-
|
7
|
-
attr_reader :session_id
|
8
|
-
attr_reader :current_message
|
9
|
-
attr_reader :conversations
|
10
|
-
attr_reader :history
|
11
|
-
|
12
|
-
def initialize(id, history=Sinbotra::Bot::DefaultMessageHistory.new)
|
13
|
-
@id = id
|
14
|
-
@history = history
|
15
|
-
@session_id = nil
|
16
|
-
@conversations = []
|
17
|
-
start_session
|
18
|
-
end
|
19
|
-
|
20
|
-
def handle_message(msg)
|
21
|
-
@current_message = msg
|
22
|
-
history.add_message(msg)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Sessions
|
5
|
+
Conversation = Struct.new(:id, :step)
|
26
6
|
|
27
|
-
|
28
|
-
|
29
|
-
# remove user while not removing history
|
30
|
-
def destroy
|
31
|
-
end_session
|
32
|
-
end
|
33
|
-
|
34
|
-
# Conversations
|
35
|
-
|
36
|
-
def start_conversation(convo)
|
37
|
-
conversations.unshift(convo)
|
38
|
-
convo.start
|
39
|
-
end
|
40
|
-
|
41
|
-
def in_conversation?
|
42
|
-
!current_conversation.nil?
|
43
|
-
end
|
7
|
+
attr_reader :id
|
8
|
+
attr_reader :conversation
|
44
9
|
|
45
|
-
def
|
46
|
-
|
10
|
+
def initialize(id)
|
11
|
+
@id = id
|
47
12
|
end
|
48
13
|
|
49
|
-
|
50
|
-
|
51
|
-
def start_session
|
52
|
-
@session_id = SecureRandom.uuid
|
14
|
+
def end_conversation
|
15
|
+
@conversation = nil
|
53
16
|
end
|
54
17
|
|
55
|
-
def
|
56
|
-
@
|
18
|
+
def update_conversation(id, step)
|
19
|
+
@conversation = Conversation.new(id, step)
|
57
20
|
end
|
58
21
|
end
|
59
22
|
end
|
@@ -1,22 +1,48 @@
|
|
1
1
|
class Sinbotra::Bot
|
2
2
|
class UserRepo
|
3
3
|
class << self
|
4
|
-
def connect
|
5
|
-
@users ||=
|
4
|
+
def connect(store=UserStore.new)
|
5
|
+
@users ||= store
|
6
6
|
end
|
7
7
|
|
8
|
-
def find_or_create(
|
8
|
+
def find_or_create(id)
|
9
9
|
user = @users[id]
|
10
10
|
if user.nil?
|
11
|
-
user =
|
12
|
-
@users[id] = user
|
11
|
+
user = create(id)
|
13
12
|
end
|
14
13
|
user
|
15
14
|
end
|
16
15
|
|
16
|
+
def create(id)
|
17
|
+
user = Sinbotra::Bot::User.new(id)
|
18
|
+
update(user)
|
19
|
+
user
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_step!(user)
|
23
|
+
step = user.conversation.step + 1
|
24
|
+
update_conversation(user, user.conversation.id, step)
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_conversation(user_id, convo_id)
|
28
|
+
user = find_or_create(user_id)
|
29
|
+
user = update_conversation(user: user, convo_id: convo_id, step: 0)
|
30
|
+
user
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_conversation(user:, convo_id:, step:)
|
34
|
+
user.update_conversation(convo_id, step)
|
35
|
+
update(user)
|
36
|
+
user
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(user)
|
40
|
+
@users[user.id] = user
|
41
|
+
end
|
42
|
+
|
17
43
|
def messages(id)
|
18
|
-
|
19
|
-
user
|
44
|
+
# Get messages from message store
|
45
|
+
$logger.debug("WIP user messages retrieval")
|
20
46
|
end
|
21
47
|
end
|
22
48
|
end
|