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