socrates 0.1.1 → 0.1.2

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: 7aa5675086ee96c3c8a47ae71f43f3eeb834432c
4
- data.tar.gz: 4ac0a6948f1b1c46a0adbba2fe82525016e452b2
3
+ metadata.gz: 18f251a1edba1096bdb4e8e1e93a2f9662100da0
4
+ data.tar.gz: e24c7497b5ac98e6d9b0c53830ede79a9dbcbee6
5
5
  SHA512:
6
- metadata.gz: ef7c44bf3192f787974b132c2b6291c82bc8c864a219727d6bb5e54c89393946379dd6b0dd8b674608d90e35a8b61541afc75a5d8b026708c16427d53d2e0995
7
- data.tar.gz: fd2a4d81d1ffeb91abf02f0dd48d72a619da2b2cdf64d8c932bb1ff315897039509abc1c858c8407f607215c0439155e1a28f760f4136146366ee4a392d36b78
6
+ metadata.gz: ddf938c670c619986a693c7f52e9a0d35304a6757f25a7031f013d55ecdb1cbcc13c75144ba4ea286664e478a127605e6b208fd210d2416e06a6062f27e84442
7
+ data.tar.gz: 24d70a3a10cc50f47f829f15a7c51ee96f2699ad4c1c4551800a67f66e1513f86b7d1b1749acc7a019671180be2607e6da21914a710ba1e104ab0bb6378f3ef7
@@ -1,34 +1,53 @@
1
- class ConsoleAdapter
2
- CLIENT_ID = "CONSOLE"
1
+ module Socrates
2
+ module Adapters
3
+ class ConsoleAdapter
4
+ CLIENT_ID = "CONSOLE"
3
5
 
4
- def initialize(name: "@socrates")
5
- @name = name
6
- end
6
+ attr_accessor :email, :users
7
7
 
8
- def client_id_from_context(_context)
9
- CLIENT_ID
10
- end
8
+ def initialize(name: "@socrates")
9
+ @name = name
10
+ @users = []
11
+ end
11
12
 
12
- def send_message(message, *)
13
- puts "\n#{colorize(@name, "32;1")}: #{message}"
14
- end
13
+ def client_id_from_context(_context)
14
+ CLIENT_ID
15
+ end
15
16
 
16
- def send_direct_message(message, user, *)
17
- name =
18
- if user.respond_to?(:name)
19
- user.name
20
- elsif user.respond_to?(:id)
21
- user.id
22
- else
23
- user
17
+ def send_message(message, *)
18
+ puts "\n#{colorize(@name, "32;1")}: #{message}"
24
19
  end
25
20
 
26
- puts "\n[DM] #{colorize(name, "34;1")}: #{message}"
27
- end
21
+ def send_direct_message(message, user, *)
22
+ name =
23
+ if user.respond_to?(:name)
24
+ user.name
25
+ elsif user.respond_to?(:id)
26
+ user.id
27
+ else
28
+ user
29
+ end
28
30
 
29
- private
31
+ puts "\n[DM] #{colorize(name, "34;1")}: #{message}"
32
+ end
30
33
 
31
- def colorize(str, color_code)
32
- "\e[#{color_code}m#{str}\e[0m"
34
+ def add_user(id: nil, name: nil, first: nil, last: nil, email: nil)
35
+ users << User.new(id, name, Profile.new(first, last, email))
36
+ end
37
+
38
+ def users_list
39
+ Response.new(users)
40
+ end
41
+
42
+ def lookup_email(*)
43
+ email
44
+ end
45
+
46
+ private
47
+
48
+ def colorize(str, color_code)
49
+ "\e[#{color_code}m#{str}\e[0m"
50
+ end
51
+ end
33
52
  end
34
53
  end
@@ -1,29 +1,46 @@
1
- class MemoryAdapter
2
- CLIENT_ID = "MEMORY"
1
+ module Socrates
2
+ module Adapters
3
+ class MemoryAdapter
4
+ CLIENT_ID = "MEMORY"
3
5
 
4
- attr_reader :history, :dms
6
+ attr_reader :history, :dms
7
+ attr_accessor :email, :users
5
8
 
6
- def initialize
7
- @history = []
8
- @dms = {}
9
- end
9
+ def initialize
10
+ @history = []
11
+ @dms = Hash.new { |hash, key| hash[key] = [] }
12
+ @users = []
13
+ end
10
14
 
11
- def client_id_from_context(_context)
12
- CLIENT_ID
13
- end
15
+ def client_id_from_context(_context)
16
+ CLIENT_ID
17
+ end
14
18
 
15
- def send_message(message, *)
16
- @history << message
17
- end
19
+ def send_message(message, *)
20
+ @history << message
21
+ end
18
22
 
19
- def send_direct_message(message, user, *)
20
- user = user.id if user.respond_to?(:id)
23
+ def send_direct_message(message, user, *)
24
+ user = user.id if user.respond_to?(:id)
21
25
 
22
- @dms[user] = [] unless @dms.key?(user)
23
- @dms[user] << message
24
- end
26
+ @dms[user] << message
27
+ end
28
+
29
+ def last_message
30
+ @history.last
31
+ end
32
+
33
+ def add_user(id: nil, name: nil, first: nil, last: nil, email: nil)
34
+ users << User.new(id, name, Profile.new(first, last, email))
35
+ end
36
+
37
+ def users_list
38
+ Response.new(users)
39
+ end
25
40
 
26
- def last_message
27
- @history.last
41
+ def lookup_email(*)
42
+ email
43
+ end
44
+ end
28
45
  end
29
46
  end
@@ -1,33 +1,47 @@
1
- class SlackAdapter
2
- def initialize(slack_real_time_client)
3
- @slack_real_time_client = slack_real_time_client
4
- end
1
+ module Socrates
2
+ module Adapters
3
+ class SlackAdapter
4
+ def initialize(slack_real_time_client)
5
+ @slack_real_time_client = slack_real_time_client
6
+ end
5
7
 
6
- def client_id_from_context(context)
7
- context&.user
8
- end
8
+ def client_id_from_context(context)
9
+ context&.user
10
+ end
9
11
 
10
- def send_message(message, context:)
11
- @slack_real_time_client.message(text: message, channel: context.channel)
12
- end
12
+ def send_message(message, context:)
13
+ @slack_real_time_client.message(text: message, channel: context.channel)
14
+ end
13
15
 
14
- def send_direct_message(message, user, *)
15
- user = user.id if user.respond_to?(:id)
16
+ def send_direct_message(message, user, *)
17
+ user = user.id if user.respond_to?(:id)
16
18
 
17
- im_channel = lookup_im_channel(user)
19
+ im_channel = lookup_im_channel(user)
18
20
 
19
- @slack_real_time_client.message(text: message, channel: im_channel)
20
- end
21
+ @slack_real_time_client.message(text: message, channel: im_channel)
22
+ end
23
+
24
+ def users_list
25
+ client = @slack_real_time_client.web_client
26
+ client.users_list
27
+ end
28
+
29
+ def lookup_email(context:)
30
+ client = @slack_real_time_client.web_client
31
+ client.users_info(user: context.user)
32
+ end
21
33
 
22
- private
34
+ private
23
35
 
24
- def lookup_im_channel(user)
25
- im = @slack_real_time_client.ims.values.find { |i| i.user == user }
36
+ def lookup_im_channel(user)
37
+ im = @slack_real_time_client.ims.values.find { |i| i.user == user }
26
38
 
27
- return im if im.present?
39
+ return im if im.present?
28
40
 
29
- # Start a new conversation with this user.
30
- response = @slack_real_time_client.web_client.im_open(user: user.id)
31
- response.channel.id
41
+ # Start a new conversation with this user.
42
+ response = @slack_real_time_client.web_client.im_open(user: user.id)
43
+ response.channel.id
44
+ end
45
+ end
32
46
  end
33
47
  end
@@ -0,0 +1,7 @@
1
+ module Socrates
2
+ module Adapters
3
+ Response = Struct.new(:members)
4
+ User = Struct.new(:id, :name, :profile)
5
+ Profile = Struct.new(:first_name, :last_name, :email)
6
+ end
7
+ end
@@ -1,15 +1,12 @@
1
1
  module Socrates
2
2
  module Bots
3
3
  class CLIBot
4
- def initialize(state_factory:)
5
- @adapter = ConsoleAdapter.new
4
+ def initialize(adapter:, state_factory:)
5
+ @adapter = adapter || Adapters::ConsoleAdapter.new
6
6
  @dispatcher = Core::Dispatcher.new(adapter: @adapter, state_factory: state_factory)
7
7
  end
8
8
 
9
9
  def start
10
- # Clear out any remnants from previous runs.
11
- @storage.clear(ConsoleAdapter::CLIENT_ID)
12
-
13
10
  while (input = gets.chomp)
14
11
  @dispatcher.dispatch(message: input)
15
12
  end
@@ -13,7 +13,7 @@ module Socrates
13
13
  end
14
14
 
15
15
  @slack_client = Slack::RealTime::Client.new
16
- @adapter = SlackAdapter.new(@slack_client)
16
+ @adapter = Adapters::SlackAdapter.new(@slack_client)
17
17
  @dispatcher = Core::Dispatcher.new(adapter: @adapter, state_factory: state_factory)
18
18
  end
19
19
 
@@ -5,6 +5,7 @@ module Socrates
5
5
  attr_accessor :view_path
6
6
  attr_accessor :storage
7
7
  attr_accessor :error_message
8
+ attr_accessor :expired_timeout # seconds
8
9
  attr_accessor :logger
9
10
  end
10
11
 
@@ -63,34 +63,51 @@ module Socrates
63
63
 
64
64
  DEFAULT_ERROR_MESSAGE = "Sorry, an error occurred. We'll have to start over..."
65
65
 
66
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
66
67
  def fetch_snapshot(client_id)
67
- state_data =
68
- if @storage.has_key?(client_id)
69
- begin
70
- snapshot = @storage.get(client_id)
71
- StateData.deserialize(snapshot)
72
- rescue => e
73
- @logger.warn "Error while fetching snapshot for client id '#{client_id}', resetting state: #{e.message}"
74
- @logger.warn e
75
- end
68
+ if @storage.has_key?(client_id)
69
+ begin
70
+ snapshot = @storage.get(client_id)
71
+ state_data = StateData.deserialize(snapshot)
72
+ rescue => e
73
+ @logger.warn "Error while fetching snapshot for client id '#{client_id}', resetting state: #{e.message}"
74
+ @logger.warn e
76
75
  end
76
+ end
77
77
 
78
78
  state_data ||= StateData.new
79
79
 
80
80
  # If the current state is nil or END_OF_CONVERSATION, set it to the default state, which is typically a state
81
81
  # that waits for an initial command or input from the user (e.g. help, start, etc).
82
82
  if state_data.state_id.nil? || state_data.state_id == State::END_OF_CONVERSATION
83
- state_data.state_id = @state_factory.default_state
84
- state_data.state_action = :listen
83
+ default_state, default_action = @state_factory.default
84
+
85
+ state_data.state_id = default_state
86
+ state_data.state_action = default_action || :listen
87
+
88
+ # Check to see if the last interation was too long ago.
89
+ elsif state_data_expired?(state_data) && @state_factory.expired(state_data).present?
90
+ expired_state, expired_action = @state_factory.expired(state_data)
91
+
92
+ state_data.state_id = expired_state
93
+ state_data.state_action = expired_action || :ask
85
94
  end
86
95
 
87
96
  state_data
88
97
  end
98
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
89
99
 
90
100
  def persist_snapshot(client_id, state_data)
101
+ state_data.reset_elapsed_time
91
102
  @storage.put(client_id, state_data.serialize)
92
103
  end
93
104
 
105
+ def state_data_expired?(state_data)
106
+ return unless state_data.timestamp.present?
107
+
108
+ state_data.elapsed_time > (Config.expired_timeout || 30.minutes)
109
+ end
110
+
94
111
  def instantiate_state(state_data, context)
95
112
  @state_factory.build(state_data: state_data, adapter: @adapter, context: context)
96
113
  end
@@ -100,9 +117,7 @@ module Socrates
100
117
  return true if state.data.state_action == :listen
101
118
 
102
119
  # Stop transitioning if there's no state to transition to, or the conversation has ended.
103
- return true if state.data.state_id.nil? || state.data.state_id == State::END_OF_CONVERSATION
104
-
105
- false
120
+ state.data.state_id.nil? || state.data.state_id == State::END_OF_CONVERSATION
106
121
  end
107
122
 
108
123
  def handle_action_error(e, client_id, state, context)
@@ -6,15 +6,24 @@ require "yaml"
6
6
  module Socrates
7
7
  module Core
8
8
  class StateData
9
- attr_accessor :state_id, :state_action
9
+ attr_accessor :state_id, :state_action, :timestamp
10
10
 
11
11
  def initialize(state_id: nil, state_action: nil, data: {}, temporary_keys: [])
12
12
  @state_id = state_id
13
13
  @state_action = state_action
14
+ @timestamp = Time.current
14
15
  @data = data
15
16
  @temporary_keys = Set.new(temporary_keys)
16
17
  end
17
18
 
19
+ def elapsed_time
20
+ Time.current - @timestamp
21
+ end
22
+
23
+ def reset_elapsed_time
24
+ @timestamp = Time.current
25
+ end
26
+
18
27
  def keys
19
28
  @data.keys
20
29
  end
@@ -5,10 +5,14 @@ require "socrates/core/state"
5
5
  module Socrates
6
6
  module SampleStates
7
7
  class StateFactory
8
- def default_state
8
+ def default
9
9
  :get_started
10
10
  end
11
11
 
12
+ def expired(*)
13
+ :expired
14
+ end
15
+
12
16
  def build(state_data:, adapter:, context: nil)
13
17
  classname = StringHelpers.underscore_to_classname(state_data.state_id)
14
18
 
@@ -21,7 +25,7 @@ module Socrates
21
25
  include Socrates::Core::State
22
26
 
23
27
  def listen(message)
24
- case message.strip
28
+ case message.downcase
25
29
  when "help"
26
30
  transition_to :help
27
31
  when "age"
@@ -62,6 +66,16 @@ module Socrates
62
66
  end
63
67
  end
64
68
 
69
+ class Expired
70
+ include Socrates::Core::State
71
+
72
+ def ask
73
+ respond message: "I've forgotten what we're talking about, let's start over."
74
+
75
+ transition_to :help
76
+ end
77
+ end
78
+
65
79
  class AskForName
66
80
  include Socrates::Core::State
67
81
 
@@ -1,3 +1,3 @@
1
1
  module Socrates
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
data/lib/socrates.rb CHANGED
@@ -6,6 +6,7 @@ require "socrates/string_helpers"
6
6
  require "socrates/adapters/console_adapter"
7
7
  require "socrates/adapters/memory_adapter"
8
8
  require "socrates/adapters/slack_adapter"
9
+ require "socrates/adapters/stubs"
9
10
  require "socrates/storage/storage"
10
11
  require "socrates/core/state_data"
11
12
  require "socrates/core/state"
data/socrates.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency "rake", "~> 10.0"
30
30
  spec.add_development_dependency "rspec", "~> 3.5"
31
31
  spec.add_development_dependency "rubocop", "~> 0.48.1"
32
+ spec.add_development_dependency "timecop", "~> 0.8.1"
32
33
 
33
34
  spec.add_dependency "activesupport", ">= 5.0.2"
34
35
  spec.add_dependency "celluloid-io", ">= 0.17.3"
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.1
4
+ version: 0.1.2
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-04-21 00:00:00.000000000 Z
11
+ date: 2017-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.48.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.8.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: activesupport
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +173,7 @@ files:
159
173
  - lib/socrates/adapters/console_adapter.rb
160
174
  - lib/socrates/adapters/memory_adapter.rb
161
175
  - lib/socrates/adapters/slack_adapter.rb
176
+ - lib/socrates/adapters/stubs.rb
162
177
  - lib/socrates/bots/cli_bot.rb
163
178
  - lib/socrates/bots/slack_bot.rb
164
179
  - lib/socrates/config.rb