socrates 0.1.12 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/socrates/adapters/slack.rb +9 -0
- data/lib/socrates/adapters/stubs.rb +13 -7
- data/lib/socrates/bots/slack.rb +3 -10
- data/lib/socrates/configuration.rb +1 -1
- data/lib/socrates/core/dispatcher.rb +19 -19
- data/lib/socrates/core/state.rb +3 -4
- data/lib/socrates/core/state_data.rb +14 -1
- data/lib/socrates/sample_states.rb +2 -2
- data/lib/socrates/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8e7d4af57cd2c88ee489aeb393275c9eed7f1ab
|
4
|
+
data.tar.gz: 949fedfa9a56d3c6e4a7fa47f1422769cb096eca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1b2eccddb347b003af3e85bbdfdc64ad7a9f2600e0fbc98353edf3b5d24d929d98a12a45b4871d7b28353d3647a744bc65be7976d35e7ed930a84d6957f1f82
|
7
|
+
data.tar.gz: 196f7252bf042ca77d2bcbf75bb251cfe27f1cd0a204b4e5e4e2bcc4420b7da64aed944e7946e40196bdb0afc0912297f77d84fa213cf9eaa63593821ceeb5be
|
@@ -31,6 +31,15 @@ module Socrates
|
|
31
31
|
raise ArgumentError, "Must provide one of :context or :user"
|
32
32
|
end
|
33
33
|
|
34
|
+
def user_from(context:)
|
35
|
+
raise ArgumentError, "Must provide a :context" if context.nil?
|
36
|
+
raise ArgumentError, "Expected :context to respond to :user" unless context.respond_to?(:user)
|
37
|
+
|
38
|
+
client = @real_time_client.web_client
|
39
|
+
info = client.users_info(user: context.user)
|
40
|
+
info.present? ? info.user : nil
|
41
|
+
end
|
42
|
+
|
34
43
|
def send_message(message, channel)
|
35
44
|
@real_time_client.message(text: message, channel: channel)
|
36
45
|
end
|
@@ -16,11 +16,11 @@ module Socrates
|
|
16
16
|
Profile = Struct.new(:first_name, :last_name, :email)
|
17
17
|
|
18
18
|
#
|
19
|
-
# StubUserDirectory provides some simple stub behavior for adding stubbed users and querying against them. This
|
20
|
-
# to be used by stubbed versions of adapters (like Console
|
19
|
+
# StubUserDirectory provides some simple stub behavior for adding stubbed users and querying against them. This is
|
20
|
+
# to be used by the stubbed versions of adapters (like Console and Memory).
|
21
21
|
#
|
22
22
|
module StubUserDirectory
|
23
|
-
attr_accessor :
|
23
|
+
attr_accessor :default_user, :users
|
24
24
|
|
25
25
|
def initialize
|
26
26
|
@users = []
|
@@ -28,20 +28,26 @@ module Socrates
|
|
28
28
|
|
29
29
|
# rubocop:disable Metrics/ParameterLists
|
30
30
|
def add_user(id: nil, name: nil, first: nil, last: nil, email: nil, tz_offset: 0)
|
31
|
-
|
31
|
+
User.new(id, name, tz_offset, Profile.new(first, last, email)).tap do |new_user|
|
32
|
+
@users << new_user
|
33
|
+
end
|
32
34
|
end
|
33
35
|
# rubocop:enable Metrics/ParameterLists
|
34
36
|
|
37
|
+
def user_from(*)
|
38
|
+
@default_user
|
39
|
+
end
|
40
|
+
|
35
41
|
def users_list(*)
|
36
|
-
Response.new(users)
|
42
|
+
Response.new(@users)
|
37
43
|
end
|
38
44
|
|
39
45
|
def lookup_user(email:)
|
40
|
-
users.find { |user| email == user.profile&.email }
|
46
|
+
@users.find { |user| email == user.profile&.email }
|
41
47
|
end
|
42
48
|
|
43
49
|
def lookup_email(*)
|
44
|
-
email
|
50
|
+
@default_user.profile&.email
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
data/lib/socrates/bots/slack.rb
CHANGED
@@ -21,20 +21,13 @@ module Socrates
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def start
|
24
|
-
reply_to_messages = {}
|
25
|
-
|
26
24
|
@slack_client.on :message do |data|
|
27
25
|
# puts "> #{data}"
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
reply_to_messages[data.channel] = data.text
|
32
|
-
end
|
27
|
+
# Slack sends us messages from ourslves sometimes, this skips them.
|
28
|
+
next if @slack_client.self.id == data.user
|
33
29
|
|
34
|
-
|
35
|
-
if reply_to_messages[data.channel] != data.text
|
36
|
-
@dispatcher.dispatch(data.text, context: data)
|
37
|
-
end
|
30
|
+
@dispatcher.dispatch(data.text, context: data)
|
38
31
|
end
|
39
32
|
|
40
33
|
@slack_client.start!
|
@@ -23,23 +23,27 @@ module Socrates
|
|
23
23
|
def dispatch(message, context: {})
|
24
24
|
client_id = @adapter.client_id_from(context: context)
|
25
25
|
channel = @adapter.channel_from(context: context)
|
26
|
+
user = @adapter.user_from(context: context)
|
26
27
|
|
27
|
-
do_dispatch(message, client_id, channel)
|
28
|
+
do_dispatch(message, client_id, channel, user)
|
28
29
|
end
|
29
30
|
|
30
|
-
def start_conversation(user, state_id)
|
31
|
+
def start_conversation(user, state_id, message: nil)
|
31
32
|
client_id = @adapter.client_id_from(user: user)
|
32
33
|
channel = @adapter.channel_from(user: user)
|
33
34
|
|
34
|
-
|
35
|
+
# Now, we assume the user of this code does this check on their own...
|
36
|
+
# return false unless conversation_state(user).nil?
|
35
37
|
|
36
38
|
# Create state data to match the request.
|
37
39
|
state_data = Socrates::Core::StateData.new(state_id: state_id, state_action: :ask)
|
38
40
|
|
39
41
|
persist_state_data(client_id, state_data)
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
+
# Send our initial message if one was passed to us.
|
44
|
+
@adapter.send_direct_message(message, user) if message.present?
|
45
|
+
|
46
|
+
do_dispatch(nil, client_id, channel, user)
|
43
47
|
end
|
44
48
|
|
45
49
|
def conversation_state(user)
|
@@ -50,7 +54,7 @@ module Socrates
|
|
50
54
|
begin
|
51
55
|
snapshot = @storage.get(client_id)
|
52
56
|
state_data = StateData.deserialize(snapshot)
|
53
|
-
state_data = nil if
|
57
|
+
state_data = nil if state_data.expired? || state_data.finished?
|
54
58
|
rescue => e
|
55
59
|
@logger.warn "Error while fetching state_data for client id '#{client_id}'."
|
56
60
|
@logger.warn e
|
@@ -64,7 +68,7 @@ module Socrates
|
|
64
68
|
|
65
69
|
DEFAULT_ERROR_MESSAGE = "Sorry, an error occurred. We'll have to start over..."
|
66
70
|
|
67
|
-
def do_dispatch(message, client_id, channel)
|
71
|
+
def do_dispatch(message, client_id, channel, user)
|
68
72
|
message = message&.strip
|
69
73
|
|
70
74
|
@logger.info %Q(#{client_id} recv: "#{message}")
|
@@ -73,7 +77,7 @@ module Socrates
|
|
73
77
|
# more :ask actions could run, before stopping at a :listen (and waiting for the next input).
|
74
78
|
loop do
|
75
79
|
state_data = fetch_state_data(client_id)
|
76
|
-
state = instantiate_state(state_data, channel)
|
80
|
+
state = instantiate_state(state_data, channel, user)
|
77
81
|
|
78
82
|
args = [state.data.state_action]
|
79
83
|
args << message if state.data.state_action == :listen
|
@@ -100,6 +104,8 @@ module Socrates
|
|
100
104
|
# Break from the loop if there's nothing left to do, i.e. no more state transitions.
|
101
105
|
break if done_transitioning?(state)
|
102
106
|
end
|
107
|
+
|
108
|
+
true
|
103
109
|
end
|
104
110
|
|
105
111
|
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
@@ -118,14 +124,14 @@ module Socrates
|
|
118
124
|
|
119
125
|
# If the current state is nil or END_OF_CONVERSATION, set it to the default state, which is typically a state
|
120
126
|
# that waits for an initial command or input from the user (e.g. help, start, etc).
|
121
|
-
if state_data.state_id.nil? || state_data.state_id ==
|
127
|
+
if state_data.state_id.nil? || state_data.state_id == StateData::END_OF_CONVERSATION
|
122
128
|
default_state, default_action = @state_factory.default
|
123
129
|
|
124
130
|
state_data.state_id = default_state
|
125
131
|
state_data.state_action = default_action || :listen
|
126
132
|
|
127
133
|
# Check to see if the last interation was too long ago.
|
128
|
-
elsif
|
134
|
+
elsif state_data.expired? && @state_factory.expired(state_data).present?
|
129
135
|
expired_state, expired_action = @state_factory.expired(state_data)
|
130
136
|
|
131
137
|
state_data.state_id = expired_state
|
@@ -141,14 +147,8 @@ module Socrates
|
|
141
147
|
@storage.put(client_id, state_data.serialize)
|
142
148
|
end
|
143
149
|
|
144
|
-
def
|
145
|
-
|
146
|
-
|
147
|
-
state_data.elapsed_time > (Socrates.config.expired_timeout || 30.minutes)
|
148
|
-
end
|
149
|
-
|
150
|
-
def instantiate_state(state_data, channel)
|
151
|
-
@state_factory.build(state_data: state_data, adapter: @adapter, channel: channel)
|
150
|
+
def instantiate_state(state_data, channel, user)
|
151
|
+
@state_factory.build(state_data: state_data, adapter: @adapter, channel: channel, user: user)
|
152
152
|
end
|
153
153
|
|
154
154
|
def done_transitioning?(state)
|
@@ -156,7 +156,7 @@ module Socrates
|
|
156
156
|
return true if state.data.state_action == :listen
|
157
157
|
|
158
158
|
# Stop transitioning if there's no state to transition to, or the conversation has ended.
|
159
|
-
state.data.state_id.nil? || state.data.state_id ==
|
159
|
+
state.data.state_id.nil? || state.data.state_id == StateData::END_OF_CONVERSATION
|
160
160
|
end
|
161
161
|
|
162
162
|
def handle_action_error(e, client_id, state, channel)
|
data/lib/socrates/core/state.rb
CHANGED
@@ -11,10 +11,11 @@ module Socrates
|
|
11
11
|
module State
|
12
12
|
attr_reader :data, :context
|
13
13
|
|
14
|
-
def initialize(data: StateData.new, adapter:, channel:)
|
14
|
+
def initialize(data: StateData.new, adapter:, channel:, user:)
|
15
15
|
@data = data
|
16
16
|
@adapter = adapter
|
17
17
|
@channel = channel
|
18
|
+
@user = user
|
18
19
|
@next_state_id = nil
|
19
20
|
@next_state_action = nil
|
20
21
|
@logger = Socrates.config.logger || Socrates::Logger.default
|
@@ -83,7 +84,7 @@ module Socrates
|
|
83
84
|
def end_conversation
|
84
85
|
@data.clear
|
85
86
|
|
86
|
-
transition_to END_OF_CONVERSATION, action: END_OF_CONVERSATION
|
87
|
+
transition_to StateData::END_OF_CONVERSATION, action: StateData::END_OF_CONVERSATION
|
87
88
|
end
|
88
89
|
|
89
90
|
def ask
|
@@ -96,8 +97,6 @@ module Socrates
|
|
96
97
|
|
97
98
|
private
|
98
99
|
|
99
|
-
END_OF_CONVERSATION = :__end__
|
100
|
-
|
101
100
|
def next_action(current_action)
|
102
101
|
(%i[ask listen] - [current_action]).first
|
103
102
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
require "hashie"
|
2
2
|
require "json"
|
3
3
|
require "yaml"
|
4
|
-
require "active_support/core_ext/time"
|
4
|
+
require "active_support/core_ext/numeric/time"
|
5
|
+
require "socrates/configuration"
|
5
6
|
|
6
7
|
module Socrates
|
7
8
|
module Core
|
8
9
|
class StateData
|
10
|
+
END_OF_CONVERSATION = :__end__
|
11
|
+
|
9
12
|
attr_accessor :state_id, :state_action, :last_interaction_timestamp
|
10
13
|
|
11
14
|
def initialize(state_id: nil, state_action: nil, data: {})
|
@@ -15,6 +18,16 @@ module Socrates
|
|
15
18
|
@temporary_keys = []
|
16
19
|
end
|
17
20
|
|
21
|
+
def finished?
|
22
|
+
@state_id.nil? || @state_id == END_OF_CONVERSATION
|
23
|
+
end
|
24
|
+
|
25
|
+
def expired?
|
26
|
+
return false unless last_interaction_timestamp.present?
|
27
|
+
|
28
|
+
elapsed_time > (Socrates.config.expired_timeout || 30.minutes)
|
29
|
+
end
|
30
|
+
|
18
31
|
def elapsed_time
|
19
32
|
Time.current - @last_interaction_timestamp
|
20
33
|
end
|
@@ -14,11 +14,11 @@ module Socrates
|
|
14
14
|
:expired
|
15
15
|
end
|
16
16
|
|
17
|
-
def build(state_data:, adapter:, channel:)
|
17
|
+
def build(state_data:, adapter:, channel:, user:)
|
18
18
|
classname = StringHelpers.underscore_to_classname(state_data.state_id)
|
19
19
|
|
20
20
|
Object.const_get("Socrates::SampleStates::#{classname}")
|
21
|
-
.new(data: state_data, adapter: adapter, channel: channel)
|
21
|
+
.new(data: state_data, adapter: adapter, channel: channel, user: user)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
data/lib/socrates/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: socrates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|