xip 0.0.1 → 2.0.0.beta2

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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +116 -0
  3. data/.gitignore +12 -0
  4. data/CHANGELOG.md +135 -0
  5. data/Gemfile +4 -1
  6. data/Gemfile.lock +65 -15
  7. data/LICENSE +6 -4
  8. data/README.md +51 -1
  9. data/VERSION +1 -0
  10. data/bin/xip +3 -11
  11. data/lib/xip.rb +1 -3
  12. data/lib/xip/base.rb +189 -0
  13. data/lib/xip/cli.rb +273 -0
  14. data/lib/xip/cli_base.rb +24 -0
  15. data/lib/xip/commands/command.rb +13 -0
  16. data/lib/xip/commands/console.rb +74 -0
  17. data/lib/xip/commands/server.rb +63 -0
  18. data/lib/xip/configuration.rb +56 -0
  19. data/lib/xip/controller/callbacks.rb +63 -0
  20. data/lib/xip/controller/catch_all.rb +84 -0
  21. data/lib/xip/controller/controller.rb +274 -0
  22. data/lib/xip/controller/dev_jumps.rb +40 -0
  23. data/lib/xip/controller/dynamic_delay.rb +61 -0
  24. data/lib/xip/controller/helpers.rb +128 -0
  25. data/lib/xip/controller/interrupt_detect.rb +99 -0
  26. data/lib/xip/controller/messages.rb +283 -0
  27. data/lib/xip/controller/nlp.rb +49 -0
  28. data/lib/xip/controller/replies.rb +281 -0
  29. data/lib/xip/controller/unrecognized_message.rb +61 -0
  30. data/lib/xip/core_ext.rb +5 -0
  31. data/lib/xip/core_ext/numeric.rb +10 -0
  32. data/lib/xip/core_ext/string.rb +18 -0
  33. data/lib/xip/dispatcher.rb +68 -0
  34. data/lib/xip/errors.rb +55 -0
  35. data/lib/xip/flow/base.rb +69 -0
  36. data/lib/xip/flow/specification.rb +56 -0
  37. data/lib/xip/flow/state.rb +82 -0
  38. data/lib/xip/generators/builder.rb +41 -0
  39. data/lib/xip/generators/builder/.gitignore +30 -0
  40. data/lib/xip/generators/builder/Gemfile +19 -0
  41. data/lib/xip/generators/builder/Procfile.dev +2 -0
  42. data/lib/xip/generators/builder/README.md +9 -0
  43. data/lib/xip/generators/builder/Rakefile +2 -0
  44. data/lib/xip/generators/builder/bot/controllers/bot_controller.rb +55 -0
  45. data/lib/xip/generators/builder/bot/controllers/catch_alls_controller.rb +21 -0
  46. data/lib/xip/generators/builder/bot/controllers/concerns/.keep +0 -0
  47. data/lib/xip/generators/builder/bot/controllers/goodbyes_controller.rb +9 -0
  48. data/lib/xip/generators/builder/bot/controllers/hellos_controller.rb +9 -0
  49. data/lib/xip/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  50. data/lib/xip/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  51. data/lib/xip/generators/builder/bot/helpers/bot_helper.rb +2 -0
  52. data/lib/xip/generators/builder/bot/models/bot_record.rb +3 -0
  53. data/lib/xip/generators/builder/bot/models/concerns/.keep +0 -0
  54. data/lib/xip/generators/builder/bot/replies/catch_alls/level1.yml +2 -0
  55. data/lib/xip/generators/builder/bot/replies/goodbyes/say_goodbye.yml +2 -0
  56. data/lib/xip/generators/builder/bot/replies/hellos/say_hello.yml +2 -0
  57. data/lib/xip/generators/builder/config.ru +4 -0
  58. data/lib/xip/generators/builder/config/boot.rb +6 -0
  59. data/lib/xip/generators/builder/config/database.yml +25 -0
  60. data/lib/xip/generators/builder/config/environment.rb +2 -0
  61. data/lib/xip/generators/builder/config/flow_map.rb +25 -0
  62. data/lib/xip/generators/builder/config/initializers/autoload.rb +8 -0
  63. data/lib/xip/generators/builder/config/initializers/inflections.rb +16 -0
  64. data/lib/xip/generators/builder/config/puma.rb +25 -0
  65. data/lib/xip/generators/builder/config/services.yml +35 -0
  66. data/lib/xip/generators/builder/config/sidekiq.yml +3 -0
  67. data/lib/xip/generators/builder/db/seeds.rb +7 -0
  68. data/lib/xip/generators/generate.rb +39 -0
  69. data/lib/xip/generators/generate/flow/controllers/controller.tt +7 -0
  70. data/lib/xip/generators/generate/flow/helpers/helper.tt +3 -0
  71. data/lib/xip/generators/generate/flow/replies/ask_example.tt +9 -0
  72. data/lib/xip/helpers/redis.rb +40 -0
  73. data/lib/xip/jobs.rb +9 -0
  74. data/lib/xip/lock.rb +82 -0
  75. data/lib/xip/logger.rb +9 -3
  76. data/lib/xip/migrations/configurator.rb +73 -0
  77. data/lib/xip/migrations/generators.rb +16 -0
  78. data/lib/xip/migrations/railtie_config.rb +14 -0
  79. data/lib/xip/migrations/tasks.rb +43 -0
  80. data/lib/xip/nlp/client.rb +21 -0
  81. data/lib/xip/nlp/result.rb +56 -0
  82. data/lib/xip/reloader.rb +89 -0
  83. data/lib/xip/reply.rb +36 -0
  84. data/lib/xip/scheduled_reply.rb +18 -0
  85. data/lib/xip/server.rb +63 -0
  86. data/lib/xip/service_message.rb +17 -0
  87. data/lib/xip/service_reply.rb +44 -0
  88. data/lib/xip/services/base_client.rb +24 -0
  89. data/lib/xip/services/base_message_handler.rb +27 -0
  90. data/lib/xip/services/base_reply_handler.rb +72 -0
  91. data/lib/xip/services/jobs/handle_message_job.rb +21 -0
  92. data/lib/xip/session.rb +203 -0
  93. data/lib/xip/version.rb +7 -1
  94. data/logo.svg +17 -0
  95. data/spec/configuration_spec.rb +93 -0
  96. data/spec/controller/callbacks_spec.rb +217 -0
  97. data/spec/controller/catch_all_spec.rb +154 -0
  98. data/spec/controller/controller_spec.rb +889 -0
  99. data/spec/controller/dynamic_delay_spec.rb +70 -0
  100. data/spec/controller/helpers_spec.rb +119 -0
  101. data/spec/controller/interrupt_detect_spec.rb +171 -0
  102. data/spec/controller/messages_spec.rb +744 -0
  103. data/spec/controller/nlp_spec.rb +93 -0
  104. data/spec/controller/replies_spec.rb +694 -0
  105. data/spec/controller/unrecognized_message_spec.rb +168 -0
  106. data/spec/dispatcher_spec.rb +79 -0
  107. data/spec/flow/flow_spec.rb +82 -0
  108. data/spec/flow/state_spec.rb +109 -0
  109. data/spec/helpers/redis_spec.rb +77 -0
  110. data/spec/lock_spec.rb +100 -0
  111. data/spec/nlp/client_spec.rb +23 -0
  112. data/spec/nlp/result_spec.rb +57 -0
  113. data/spec/replies/hello.yml.erb +15 -0
  114. data/spec/replies/messages/say_hola.yml+facebook.erb +6 -0
  115. data/spec/replies/messages/say_hola.yml+twilio.erb +6 -0
  116. data/spec/replies/messages/say_hola.yml.erb +6 -0
  117. data/spec/replies/messages/say_howdy_with_dynamic.yml +79 -0
  118. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  119. data/spec/replies/messages/say_offer.yml +6 -0
  120. data/spec/replies/messages/say_offer_with_dynamic.yml +6 -0
  121. data/spec/replies/messages/say_oi.yml.erb +15 -0
  122. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  123. data/spec/replies/messages/say_randomize_text.yml +10 -0
  124. data/spec/replies/messages/say_yo.yml +6 -0
  125. data/spec/replies/messages/say_yo.yml+twitter +6 -0
  126. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  127. data/spec/reply_spec.rb +61 -0
  128. data/spec/scheduled_reply_spec.rb +23 -0
  129. data/spec/service_reply_spec.rb +92 -0
  130. data/spec/session_spec.rb +366 -0
  131. data/spec/spec_helper.rb +22 -66
  132. data/spec/support/alternate_helpers/foo_helper.rb +5 -0
  133. data/spec/support/controllers/vaders_controller.rb +24 -0
  134. data/spec/support/helpers/fun/games_helper.rb +7 -0
  135. data/spec/support/helpers/fun/pdf_helper.rb +7 -0
  136. data/spec/support/helpers/standalone_helper.rb +5 -0
  137. data/spec/support/helpers_typo/users_helper.rb +2 -0
  138. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  139. data/spec/support/nlp_clients/luis.rb +9 -0
  140. data/spec/support/nlp_results/luis_result.rb +163 -0
  141. data/spec/support/sample_messages.rb +66 -0
  142. data/spec/support/services.yml +31 -0
  143. data/spec/support/services_with_erb.yml +31 -0
  144. data/spec/version_spec.rb +16 -0
  145. data/xip.gemspec +25 -14
  146. metadata +320 -18
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ class Errors < StandardError
5
+
6
+ class ConfigurationError < Errors
7
+ end
8
+
9
+ class ReplyFormatNotSupported < Errors
10
+ end
11
+
12
+ class ServiceImpaired < Errors
13
+ end
14
+
15
+ class ServiceError < Errors
16
+ end
17
+
18
+ class ServiceNotRecognized < Errors
19
+ end
20
+
21
+ class ControllerRoutingNotImplemented < Errors
22
+ end
23
+
24
+ class UndefinedVariable < Errors
25
+ end
26
+
27
+ class RedisNotConfigured < Errors
28
+ end
29
+
30
+ class InvalidStateTransition < Errors
31
+ end
32
+
33
+ class ReplyNotFound < Errors
34
+ end
35
+
36
+ class UnrecognizedMessage < Errors
37
+ end
38
+
39
+ class FlowError < Errors
40
+ end
41
+
42
+ class FlowDefinitionError < Errors
43
+ end
44
+
45
+ class InvalidSessionID < Errors
46
+ end
47
+
48
+ class UserOptOut < Errors
49
+ end
50
+
51
+ class ReservedHomophoneUsed < Errors
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xip/flow/specification'
4
+ require 'xip/flow/state'
5
+
6
+ module Xip
7
+ module Flow
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ def flow(flow_name, &specification)
13
+ flow_spec[flow_name.to_sym] = Specification.new(flow_name, &specification)
14
+ end
15
+ end
16
+
17
+ included do
18
+ class_attribute :flow_spec, default: {}
19
+
20
+ attr_accessor :flow, :flow_state, :user_id
21
+
22
+ def current_state
23
+ res = self.spec.states[@flow_state.to_sym] if @flow_state
24
+ res || self.spec.initial_state
25
+ end
26
+
27
+ def current_flow
28
+ @flow || self.class.flow_spec.keys.first
29
+ end
30
+
31
+ def spec
32
+ self.class.flow_spec[current_flow]
33
+ end
34
+
35
+ def states
36
+ self.spec.states.keys
37
+ end
38
+
39
+ def init(flow:, state:)
40
+ new_flow = flow.to_sym
41
+ new_state = state.to_sym
42
+
43
+ unless state_exists?(potential_flow: new_flow, potential_state: new_state)
44
+ raise(Xip::Errors::InvalidStateTransition, "Unknown state '#{new_state}' for '#{new_flow}' flow")
45
+ end
46
+
47
+ @flow = new_flow
48
+ @flow_state = new_state
49
+
50
+ self
51
+ end
52
+
53
+ private
54
+
55
+ def flow_and_state
56
+ [current_flow, current_state].join(Xip::Session::SLUG_SEPARATOR)
57
+ end
58
+
59
+ def state_exists?(potential_flow:, potential_state:)
60
+ if self.class.flow_spec[potential_flow].present?
61
+ self.class.flow_spec[potential_flow].states.include?(potential_state)
62
+ else
63
+ raise(Xip::Errors::InvalidStateTransition, "Unknown flow '#{potential_flow}'")
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Flow
5
+ class Specification
6
+ attr_reader :flow_name
7
+ attr_accessor :states, :initial_state
8
+
9
+ def initialize(flow_name, &specification)
10
+ @states = Hash.new
11
+ @flow_name = flow_name
12
+ instance_eval(&specification)
13
+ end
14
+
15
+ def state_names
16
+ states.keys
17
+ end
18
+
19
+ private
20
+
21
+ def state(name, fails_to: nil, redirects_to: nil, **opts)
22
+ fail_state = get_fail_or_redirect_state(fails_to)
23
+ redirect_state = get_fail_or_redirect_state(redirects_to)
24
+
25
+ new_state = Xip::Flow::State.new(
26
+ name: name,
27
+ spec: self,
28
+ fails_to: fail_state,
29
+ redirects_to: redirect_state,
30
+ opts: opts
31
+ )
32
+
33
+ @initial_state = new_state if @states.empty?
34
+ @states[name.to_sym] = new_state
35
+ end
36
+
37
+ def get_fail_or_redirect_state(specified_state)
38
+ if specified_state.present?
39
+ session = Xip::Session.new
40
+
41
+ if Xip::Session.is_a_session_string?(specified_state)
42
+ session.session = specified_state
43
+ else
44
+ session.session = Xip::Session.canonical_session_slug(
45
+ flow: flow_name,
46
+ state: specified_state
47
+ )
48
+ end
49
+
50
+ return session
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Flow
5
+ class State
6
+
7
+ include Comparable
8
+
9
+ attr_accessor :name
10
+ attr_reader :spec, :fails_to, :redirects_to, :opts
11
+
12
+ def initialize(name:, spec:, fails_to: nil, redirects_to: nil, opts:)
13
+ if fails_to.present? && !fails_to.is_a?(Xip::Session)
14
+ raise(ArgumentError, 'fails_to state should be a Xip::Session')
15
+ end
16
+
17
+ if redirects_to.present? && !redirects_to.is_a?(Xip::Session)
18
+ raise(ArgumentError, 'redirects_to state should be a Xip::Session')
19
+ end
20
+
21
+ @name, @spec = name, spec
22
+ @fails_to, @redirects_to, @opts = fails_to, redirects_to, opts
23
+ end
24
+
25
+ def <=>(other_state)
26
+ state_position(self) <=> state_position(other_state)
27
+ end
28
+
29
+ def +(steps)
30
+ if steps < 0
31
+ new_position = state_position(self) + steps
32
+
33
+ # we don't want to allow the array index to wrap here so we return
34
+ # the first state instead
35
+ if new_position < 0
36
+ new_state = spec.states.keys.first
37
+ else
38
+ new_state = spec.states.keys.at(new_position)
39
+ end
40
+ else
41
+ new_state = spec.states.keys[state_position(self) + steps]
42
+
43
+ # we may have been told to access an out-of-bounds state
44
+ # return the last state
45
+ if new_state.blank?
46
+ new_state = spec.states.keys.last
47
+ end
48
+ end
49
+
50
+ new_state
51
+ end
52
+
53
+ def -(steps)
54
+ if steps < 0
55
+ return self + steps.abs
56
+ else
57
+ return self + (-steps)
58
+ end
59
+ end
60
+
61
+ def to_s
62
+ "#{name}"
63
+ end
64
+
65
+ def to_sym
66
+ name.to_sym
67
+ end
68
+
69
+ private
70
+
71
+ def state_position(state)
72
+ states = spec.states.keys
73
+
74
+ unless states.include?(state.to_sym)
75
+ raise(ArgumentError, "state `#{state}' does not exist")
76
+ end
77
+
78
+ states.index(state.to_sym)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor/group'
5
+
6
+ module Xip
7
+ module Generators
8
+ class Builder < Thor::Group
9
+ include Thor::Actions
10
+
11
+ argument :name
12
+
13
+ def self.source_root
14
+ File.dirname(__FILE__) + "/builder"
15
+ end
16
+
17
+ def create_bot_directory
18
+ empty_directory(name)
19
+ end
20
+
21
+ def create_bot_structure
22
+ directory('bot', "#{name}/bot")
23
+ directory('config', "#{name}/config")
24
+ directory('db', "#{name}/db")
25
+
26
+ # Miscellaneous Files
27
+ copy_file "config.ru", "#{name}/config.ru"
28
+ copy_file "Rakefile", "#{name}/Rakefile"
29
+ copy_file "Gemfile", "#{name}/Gemfile"
30
+ copy_file "README.md", "#{name}/README.md"
31
+ copy_file "Procfile.dev", "#{name}/Procfile.dev"
32
+ copy_file ".gitignore", "#{name}/.gitignore"
33
+ end
34
+
35
+ def change_directory_bundle
36
+ puts run("cd #{name} && bundle install")
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile '~/.gitignore_global'
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore the default SQLite database.
11
+ /db/*.sqlite3
12
+ /db/*.sqlite3-journal
13
+
14
+ # Ignore all logfiles and tempfiles.
15
+ /log/*
16
+ /tmp/*
17
+ !/log/.keep
18
+ !/tmp/.keep
19
+
20
+ # Ignore uploaded files in development
21
+ /storage/*
22
+
23
+ /node_modules
24
+ /yarn-error.log
25
+
26
+ /public/assets
27
+ .byebug_history
28
+
29
+ # Ignore master key for decrypting credentials and more.
30
+ /config/master.key
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'xip', '~> 2.0'
4
+
5
+ gem 'railties', '~> 6.1'
6
+ gem 'activerecord', '~> 6.1'
7
+ # Use sqlite3 as the database for Active Record
8
+ gem 'sqlite3'
9
+
10
+ # Uncomment to enable the Xip Facebook Driver
11
+ # gem 'xip-facebook'
12
+
13
+ # Uncomment to enable the Xip Twilio SMS Driver
14
+ # gem 'xip-twilio'
15
+
16
+ group :development do
17
+ gem 'foreman'
18
+ gem 'listen', '~> 3.3'
19
+ end
@@ -0,0 +1,2 @@
1
+ web: bundle exec puma -C config/puma.rb
2
+ sidekiq: bundle exec sidekiq -C config/sidekiq.yml -q xip_webhooks -q xip_replies -r ./config/boot.rb
@@ -0,0 +1,9 @@
1
+ # <a href='https://xipkit.com'><img src='https://cdn.xipkit.com/logo-dark.svg' width='400' alt='Xip Logo' aria-label='xipkit.com' /></a>
2
+
3
+ To boot this bot locally, we recommend the following:
4
+
5
+ 1. `bundle`
6
+ 2. Start your local Redis server
7
+ 3. `xip s`
8
+
9
+ For more information, please check out the [Xip documentation](https://xipkit.com/docs).
@@ -0,0 +1,2 @@
1
+ require_relative 'config/boot'
2
+ Xip::Migrations::Tasks.load_tasks
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BotController < Xip::Controller
4
+
5
+ helper :all
6
+
7
+ def route
8
+ if current_message.payload.present?
9
+ handle_payloads
10
+ # Clear out the payload to prevent duplicate handling
11
+ current_message.payload = nil
12
+ return
13
+ end
14
+
15
+ # Allow devs to jump around flows and states by typing:
16
+ # /flow_name/state_name or
17
+ # /flow_name (jumps to first state) or
18
+ # //state_name (jumps to state in current flow)
19
+ # (only works for bots in development)
20
+ return if dev_jump_detected?
21
+
22
+ if current_session.present?
23
+ step_to session: current_session
24
+ else
25
+ step_to flow: 'hello', state: 'say_hello'
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # Handle payloads globally since payload buttons remain in the chat
32
+ # and we cannot guess in which states they will be tapped.
33
+ def handle_payloads
34
+ case current_message.payload
35
+ when 'developer_restart', 'new_user'
36
+ step_to flow: 'hello', state: 'say_hello'
37
+ when 'goodbye'
38
+ step_to flow: 'goodbye'
39
+ end
40
+ end
41
+
42
+ # Automatically called when clients receive an opt-out error from
43
+ # the platform. You can write your own steps for handling.
44
+ def handle_opt_out
45
+ do_nothing
46
+ end
47
+
48
+ # Automatically called when clients receive an invalid session_id error from
49
+ # the platform. For example, attempting to text a landline.
50
+ # You can write your own steps for handling.
51
+ def handle_invalid_session_id
52
+ do_nothing
53
+ end
54
+
55
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CatchAllsController < BotController
4
+
5
+ def level1
6
+ send_replies
7
+
8
+ if fail_session.present?
9
+ step_to session: fail_session
10
+ else
11
+ step_to session: previous_session - 2.states
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def fail_session
18
+ previous_session.flow.current_state.fails_to
19
+ end
20
+
21
+ end