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