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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b189f7e8275110163865fd7fdc586ad30d7481c6
4
- data.tar.gz: fe7bdee2a73be689015c61a83981eb3fb424b6a9
3
+ metadata.gz: e8e7d4af57cd2c88ee489aeb393275c9eed7f1ab
4
+ data.tar.gz: 949fedfa9a56d3c6e4a7fa47f1422769cb096eca
5
5
  SHA512:
6
- metadata.gz: 238271e59d1a7f141a104092ad4a689882f75e20c85c58a081624a7666174904ecd1d173707449763f811937eb3d85603ca090259fea6be6aca54618228fae1b
7
- data.tar.gz: 327d7cc6207e3d4f1b70e3f29c65351bf7ba7a25e477de0a3eef298920b621b256604d1e1aebe94f586cc617468bf233fdb89af3968f6c6069dfc093788e963b
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 are
20
- # to be used by stubbed versions of adapters (like Console, Memory, etc).
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 :email, :users
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
- users << User.new(id, name, tz_offset, Profile.new(first, last, email))
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
@@ -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
- if data.reply_to.present?
30
- # Stash this message away because we may need it later.
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
- # Only dispatch the message if it does not match a previous reply_to message for the channel.
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!
@@ -1,4 +1,4 @@
1
- require "active_support/core_ext/time"
1
+ require "active_support/core_ext/numeric/time"
2
2
  require "socrates/logger"
3
3
  require "socrates/storage/memory"
4
4
 
@@ -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
- return false unless conversation_state(user).nil?
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
- do_dispatch(nil, client_id, channel)
42
- true
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 state_data_expired?(state_data)
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 == State::END_OF_CONVERSATION
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 state_data_expired?(state_data) && @state_factory.expired(state_data).present?
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 state_data_expired?(state_data)
145
- return false unless state_data.last_interaction_timestamp.present?
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 == State::END_OF_CONVERSATION
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)
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Socrates
2
- VERSION = "0.1.12"
2
+ VERSION = "0.1.13"
3
3
  end
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.12
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-06-30 00:00:00.000000000 Z
11
+ date: 2017-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler