bot_platform 0.1.0 → 0.2.0

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/.gitignore +8 -0
  3. data/.idea/bot_platform.iml +317 -0
  4. data/.idea/misc.xml +4 -0
  5. data/.idea/modules.xml +8 -0
  6. data/.idea/vcs.xml +6 -0
  7. data/.ruby-version +1 -0
  8. data/Gemfile.lock +1 -1
  9. data/LICENSE +21 -0
  10. data/README.md +19 -9
  11. data/bin/cli +34 -0
  12. data/docs/channels.md +7 -0
  13. data/lib/bot_platform/activity.rb +32 -0
  14. data/lib/bot_platform/adapter.rb +87 -0
  15. data/lib/bot_platform/asserts.rb +69 -0
  16. data/lib/bot_platform/boot.rb +12 -0
  17. data/lib/bot_platform/channels/base.rb +26 -0
  18. data/lib/bot_platform/channels/chatwork.rb +1 -0
  19. data/lib/bot_platform/channels/console.rb +60 -0
  20. data/lib/bot_platform/channels/facebook.rb +1 -0
  21. data/lib/bot_platform/channels/line.rb +1 -0
  22. data/lib/bot_platform/channels/lineworks.rb +102 -0
  23. data/lib/bot_platform/channels/skype.rb +1 -0
  24. data/lib/bot_platform/channels/skype_for_business.rb +1 -0
  25. data/lib/bot_platform/channels/slack.rb +15 -0
  26. data/lib/bot_platform/channels/teams.rb +15 -0
  27. data/lib/bot_platform/channels/web.rb +15 -0
  28. data/lib/bot_platform/channels/wechat.rb +1 -0
  29. data/lib/bot_platform/channels.rb +9 -0
  30. data/lib/bot_platform/cli.rb +97 -0
  31. data/lib/bot_platform/conversation_state.rb +32 -0
  32. data/lib/bot_platform/dialogs/dialog.rb +48 -0
  33. data/lib/bot_platform/dialogs/dialog_context.rb +104 -0
  34. data/lib/bot_platform/dialogs/dialog_instance.rb +15 -0
  35. data/lib/bot_platform/dialogs/dialog_result.rb +22 -0
  36. data/lib/bot_platform/dialogs/dialog_set.rb +38 -0
  37. data/lib/bot_platform/dialogs/dialog_state.rb +11 -0
  38. data/lib/bot_platform/dialogs/prompts/prompt.rb +84 -0
  39. data/lib/bot_platform/dialogs/prompts/prompt_options.rb +12 -0
  40. data/lib/bot_platform/dialogs/prompts/prompt_recognizer_result.rb +16 -0
  41. data/lib/bot_platform/dialogs/prompts/text_prompt.rb +36 -0
  42. data/lib/bot_platform/dialogs.rb +17 -0
  43. data/lib/bot_platform/message_factory.rb +9 -0
  44. data/lib/bot_platform/state/bot_state.rb +21 -0
  45. data/lib/bot_platform/state/conversation_state.rb +17 -0
  46. data/lib/bot_platform/state/user_state.rb +1 -0
  47. data/lib/bot_platform/storage/memory_storage.rb +51 -0
  48. data/lib/bot_platform/storage/mysql_storage.rb +1 -0
  49. data/lib/bot_platform/storage/redis_storage.rb +1 -0
  50. data/lib/bot_platform/storage/storageable.rb +33 -0
  51. data/lib/bot_platform/turn_context.rb +105 -0
  52. data/lib/bot_platform/version.rb +1 -1
  53. data/lib/bot_platform.rb +11 -0
  54. data/samples/dialogs/dialog_simple.rb +29 -0
  55. data/samples/echo.rb +5 -0
  56. metadata +52 -2
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform::Channels
4
+ class Web
5
+ include Base
6
+
7
+ def channel_id
8
+ "web"
9
+ end
10
+
11
+ def match_request(headers,body)
12
+ false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -0,0 +1,9 @@
1
+ require_relative 'channels/base'
2
+ require_relative 'channels/console'
3
+ require_relative 'channels/lineworks'
4
+
5
+ module BotPlatform
6
+ module Channels
7
+
8
+ end
9
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'readline'
5
+ require 'getoptlong'
6
+ require 'singleton'
7
+
8
+ module BotPlatform
9
+ class Cli
10
+ attr_accessor :bot_name, :user_name, :bot_id, :room_id, :bot_instance
11
+
12
+ def initialize(instance)
13
+ @bot_name = "bot"
14
+ @user_name = "user"
15
+ @bot_instance = instance
16
+ ENV['BOT_CHANNELS'] = 'console'
17
+
18
+ Signal.trap(:INT) do
19
+ puts "\nbye."
20
+ exit 0
21
+ end
22
+ end
23
+
24
+ def run
25
+ puts welcome_message
26
+
27
+ opts = GetoptLong.new(
28
+ ['--name', GetoptLong::OPTIONAL_ARGUMENT]
29
+ )
30
+ opts.each do |opt, arg|
31
+ case opt
32
+ when '--name' then @bot_name = arg
33
+ end
34
+ end
35
+
36
+ while cmd = Readline.readline(prompt, true)
37
+ case cmd
38
+ when "exit", "quit", '\q' then
39
+ exit
40
+ when "help", '\h', '\?' then
41
+ puts help
42
+ when 'clear', '\c' then
43
+ system 'clear'
44
+ else # bot incoming activity
45
+ unless cmd.empty?
46
+ process cmd
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def process(cmd)
53
+ headers = {"X-Bot-Platform-Bot":@bot_name}
54
+ body = {bot_id:@bot_id, room_id:@room_id}
55
+ if cmd.start_with? '/'
56
+ body[:type] = "cmd_back"
57
+ cmd.slice!(0)
58
+ body[:cmd] = cmd
59
+ else
60
+ body[:type] = "message"
61
+ body[:text] = cmd
62
+ end
63
+
64
+ instance = @bot_instance
65
+
66
+ BotPlatform::Adapter.instance.process_activity headers,body do |context|
67
+ instance.on_turn context
68
+ end
69
+
70
+ end
71
+
72
+ def prompt
73
+ return "#{@user_name}> "
74
+ end
75
+
76
+ def help
77
+ <<-USAGE
78
+ usage:
79
+ history
80
+ exit, quit, \\q - close shell and exit
81
+ help, \\h, \\? - print this usage
82
+ clear, \\c - clear the terminal screen
83
+ USAGE
84
+ end
85
+
86
+ def welcome_message
87
+ <<-WELCOME
88
+ Welcome to Bot Platform Command Line Interface (v0.1)
89
+ \\------/
90
+ _ [| o ω o|] _
91
+ / |==\\__-__/=| \\
92
+
93
+ powered by Ningfeng Yang
94
+ WELCOME
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ class ConversationState
5
+ include Singleton
6
+ attr_accessor :conversation
7
+
8
+
9
+ def initialize
10
+ user_id = turn_context.from.user_id
11
+ room_id = turn_context.from.room_id
12
+ key = "#{user_id}@#{room_id}"
13
+ user_state = MemoryStorage.instance.get(key) || {}
14
+ conversation_state = user_state[:conversation] || {}
15
+ user_state[:conversation] = conversation_state
16
+
17
+ MemoryStorage.instance.set(key, user_state)
18
+ end
19
+
20
+ def get(turn_context)
21
+ user_id = turn_context.from.user_id
22
+ room_id = turn_context.from.room_id
23
+ key = "#{user_id}@#{room_id}"
24
+
25
+ MemoryStorage.instance.get(key)[:conversation]
26
+ end
27
+
28
+ def set(context, key, value)
29
+ MemoryStorage.instance.get(context).set(key, value)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ class Dialog
6
+ include BotPlatform::Asserts
7
+ END_OF_TURN = "end_of_turn".freeze
8
+
9
+ attr_reader :id
10
+
11
+ def initialize(dialog_id)
12
+ assert_dialog_id_is_valid dialog_id
13
+
14
+ @id = dialog_id
15
+ end
16
+
17
+ # abstract
18
+ # start the instance when ready
19
+ def start(dc)
20
+ raise "unimplemented"
21
+ end
22
+
23
+ # called after user has new activity
24
+ # params:
25
+ # dc: DialogContext
26
+ def continue(dc)
27
+ # by default just stop the dialog
28
+ return dc.stop
29
+ end
30
+
31
+ # called after pre-dialog was poped
32
+ # params:
33
+ # dc: DialogContext
34
+ def resume(dc, result)
35
+ # by default just stop the dialog
36
+ stop result
37
+ end
38
+
39
+ # not-implemented
40
+ def reprompt(turn_context, instance)
41
+ end
42
+
43
+ # called by #DialogContext#stop_active_dialog if need to stop dialog
44
+ def stop(result)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ class DialogContext
6
+ include BotPlatform::Asserts
7
+
8
+ attr_accessor :dialogs, :turn_context, :dialog_stack
9
+
10
+ def initialize(dialogs, turn_context, dialog_state)
11
+ assert_dialog_set_is_valid dialogs
12
+ assert_turn_context_is_valid turn_context
13
+ assert_dialog_state_is_valid dialog_state
14
+
15
+ @dialogs = dialogs
16
+ @turn_context = turn_context
17
+ @dialog_stack = dialog_state.dialog_stack
18
+ end
19
+
20
+ def active_dialog
21
+ return nil if @dialog_stack.nil? || @dialog_stack.length==0
22
+ @dialog_stack.last
23
+ end
24
+
25
+ def start_dialog(dialog_id, options)
26
+ assert_dialog_id_is_valid dialog_id
27
+
28
+ dialog = @dialogs.find(dialog_id)
29
+ raise "dialog(id=#{dialog_id}) cannot be found." if dialog.nil?
30
+ dialog_instance = DialogInstance.new dialog_id
31
+ @dialog_stack.push dialog_instance
32
+
33
+ dialog.start self, options
34
+ end
35
+
36
+ def prompt(dialog_id, options)
37
+ assert_dialog_id_is_valid dialog_id
38
+ assert_is_not_empty options
39
+
40
+ start_dialog dialog_id, options
41
+ end
42
+
43
+ def continue_dialog
44
+ instance = active_dialog
45
+ if instance != nil
46
+ dialog = @dialogs.find instance.dialog_id
47
+ if dialog
48
+ return dialog.continue self
49
+ end
50
+ end
51
+
52
+ return DialogResult.new :empty
53
+ end
54
+
55
+ def stop_dialog(result)
56
+ if @dialog_stack.any?
57
+ @dialog_stack.pop
58
+ end
59
+ dialog = active_dialog
60
+
61
+ #previous dialog
62
+ if dialog
63
+ dialog.resume self
64
+ else
65
+ return DialogResult.new :complete, result
66
+ end
67
+ end
68
+
69
+ # stop all dialogs in the stack
70
+ def stop_all
71
+ if @dialog_stack.any?
72
+ @dialog_stack.each do |dialog|
73
+ dialog.stop
74
+ end
75
+ @dialog_stack = []
76
+ end
77
+ end
78
+
79
+ # stop active dialog and start a new dialog by given
80
+ def replace_dialog(dialog_id, options)
81
+ unless active_dialog.nil?
82
+ @dialog_stack.pop
83
+ end
84
+
85
+ return start_dialog(dialog_id, options)
86
+ end
87
+
88
+ def reprompt
89
+ dialog = active_dialog
90
+ if dialog != nil
91
+ dialog.reprompt(turn_context)
92
+ end
93
+ end
94
+
95
+ def stop_active_dialog
96
+ dialog = active_dialog
97
+ if dialog != nil
98
+ dialog.stop
99
+ @dialog_stack.pop
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ class DialogInstance
6
+ attr_reader :dialog_id
7
+ attr_accessor :state
8
+
9
+ def initialize(dialog_id)
10
+ @dialog_id = dialog_id
11
+ @state = {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ class DialogResult
6
+ STATUS_TYPES = {
7
+ :empty => 0,
8
+ :complete => 1,
9
+ :canceled => 2,
10
+ }
11
+
12
+ attr_reader :status, :result
13
+
14
+ def initialize(status, result=nil)
15
+ raise "invalid dialog result status" if !STATUS_TYPES.key?(status)
16
+
17
+ @status = status
18
+ @result = result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ class DialogSet
6
+ include BotPlatform::Asserts
7
+
8
+ attr_accessor :dialogs, :dialog_state
9
+
10
+ def initialize()
11
+ # assert_dialog_state_is_not_null dialog_state
12
+ # @dialog_state = dialog_state
13
+ @dialog_state = BotPlatform::Dialogs::DialogState.new
14
+ @dialogs = {}
15
+ end
16
+
17
+ def add(dialog)
18
+ assert_dialog_is_valid dialog
19
+ assert_dialog_is_uniq @dialogs, dialog.id
20
+
21
+ @dialogs[dialog.id.to_sym] = dialog
22
+ return self
23
+ end
24
+
25
+ def create_dialog_context(turn_context)
26
+ assert_turn_context_is_valid turn_context
27
+
28
+ state = @dialog_state
29
+
30
+ return DialogContext.new self, turn_context, state
31
+ end
32
+
33
+ def find(dialog_id)
34
+ return @dialogs[dialog_id.to_sym]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ module BotPlatform
2
+ module Dialogs
3
+ class DialogState
4
+ attr_accessor :dialog_stack
5
+
6
+ def initialize
7
+ @dialog_stack = []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BotPlatform
4
+ module Dialogs
5
+ module Prompts
6
+ class Prompt < Dialog
7
+ attr_reader :validator
8
+
9
+ def initialize(dialog_id, validtor=nil)
10
+ super(dialog_id)
11
+ @validator = validator
12
+ end
13
+
14
+ def start(dc, options)
15
+ assert_dialog_context_is_valid dc
16
+ assert_prompt_options_is_valid options
17
+
18
+ state = dc.active_dialog.state
19
+ state[:options] = options
20
+ state[:state] = {}
21
+
22
+ on_prompt(dc.turn_context, state[:state], state[:options], false)
23
+ return BotPlatform::Dialogs::DialogResult.new :complete
24
+ end
25
+
26
+ def continue(dc)
27
+ assert_dialog_context_is_valid dc
28
+
29
+ if dc.turn_context.activity.type != BotPlatform::Activity::TYPES[:message]
30
+ # do nothing
31
+ return BotPlatform::Dialogs::DialogResult.new :complete
32
+ end
33
+
34
+ instance = dc.active_dialog
35
+ state = instance.state[:state]
36
+ options = instance.state[:options]
37
+
38
+ recognized = on_recognize(dc.turn_context, state, options)
39
+
40
+ is_valid = false
41
+
42
+ if !@validator.nil?
43
+ prompt_validator_ctx = PromptValidatorContext.new dc.turn_context, recognized, state, options
44
+ is_valid = @validator.send(prompt_validator_ctx)
45
+ elsif recognized.succeeded
46
+ is_valid = true
47
+ end
48
+
49
+ if is_valid
50
+ return dc.stop_dialog(recognized.value)
51
+ else
52
+ return on_prompt(dc.turn_context, state, options, true)
53
+ end
54
+ end
55
+
56
+ def resume(dc, result=null)
57
+ assert_dialog_context_is_valid dc
58
+
59
+ reprompt(dc.turn_context, dc.active_dialog)
60
+ return BotPlatform::Dialogs::DialogResult.new :complete
61
+ end
62
+
63
+ def repromp(ttx, instance)
64
+ assert_turn_context_is_valid ttx
65
+ assert_dialog_instance_is_valid instance
66
+
67
+ state = instace.state[:state]
68
+ options = instance.state[:options]
69
+
70
+ on_prompt(ttx, state, options, false)
71
+ end
72
+
73
+ def on_prompt(turn_context, state, options, is_retry)
74
+ raise "not implemented(abstrct in super)"
75
+ end
76
+
77
+ def on_recognize(turn_context, state, options, is_retry)
78
+ raise "not implemented(abstrct in super)"
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end