socrates 0.1.12 → 0.1.13
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/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
|