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.
- checksums.yaml +4 -4
- data/.idea/.gitignore +8 -0
- data/.idea/bot_platform.iml +317 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.ruby-version +1 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +21 -0
- data/README.md +19 -9
- data/bin/cli +34 -0
- data/docs/channels.md +7 -0
- data/lib/bot_platform/activity.rb +32 -0
- data/lib/bot_platform/adapter.rb +87 -0
- data/lib/bot_platform/asserts.rb +69 -0
- data/lib/bot_platform/boot.rb +12 -0
- data/lib/bot_platform/channels/base.rb +26 -0
- data/lib/bot_platform/channels/chatwork.rb +1 -0
- data/lib/bot_platform/channels/console.rb +60 -0
- data/lib/bot_platform/channels/facebook.rb +1 -0
- data/lib/bot_platform/channels/line.rb +1 -0
- data/lib/bot_platform/channels/lineworks.rb +102 -0
- data/lib/bot_platform/channels/skype.rb +1 -0
- data/lib/bot_platform/channels/skype_for_business.rb +1 -0
- data/lib/bot_platform/channels/slack.rb +15 -0
- data/lib/bot_platform/channels/teams.rb +15 -0
- data/lib/bot_platform/channels/web.rb +15 -0
- data/lib/bot_platform/channels/wechat.rb +1 -0
- data/lib/bot_platform/channels.rb +9 -0
- data/lib/bot_platform/cli.rb +97 -0
- data/lib/bot_platform/conversation_state.rb +32 -0
- data/lib/bot_platform/dialogs/dialog.rb +48 -0
- data/lib/bot_platform/dialogs/dialog_context.rb +104 -0
- data/lib/bot_platform/dialogs/dialog_instance.rb +15 -0
- data/lib/bot_platform/dialogs/dialog_result.rb +22 -0
- data/lib/bot_platform/dialogs/dialog_set.rb +38 -0
- data/lib/bot_platform/dialogs/dialog_state.rb +11 -0
- data/lib/bot_platform/dialogs/prompts/prompt.rb +84 -0
- data/lib/bot_platform/dialogs/prompts/prompt_options.rb +12 -0
- data/lib/bot_platform/dialogs/prompts/prompt_recognizer_result.rb +16 -0
- data/lib/bot_platform/dialogs/prompts/text_prompt.rb +36 -0
- data/lib/bot_platform/dialogs.rb +17 -0
- data/lib/bot_platform/message_factory.rb +9 -0
- data/lib/bot_platform/state/bot_state.rb +21 -0
- data/lib/bot_platform/state/conversation_state.rb +17 -0
- data/lib/bot_platform/state/user_state.rb +1 -0
- data/lib/bot_platform/storage/memory_storage.rb +51 -0
- data/lib/bot_platform/storage/mysql_storage.rb +1 -0
- data/lib/bot_platform/storage/redis_storage.rb +1 -0
- data/lib/bot_platform/storage/storageable.rb +33 -0
- data/lib/bot_platform/turn_context.rb +105 -0
- data/lib/bot_platform/version.rb +1 -1
- data/lib/bot_platform.rb +11 -0
- data/samples/dialogs/dialog_simple.rb +29 -0
- data/samples/echo.rb +5 -0
- metadata +52 -2
data/docs/channels.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
class Activity
|
5
|
+
attr_reader :type, :text, :content, :options, :from, :prefix, :preview_url, :resource_id, :resource_url, :channel_id
|
6
|
+
attr_accessor :to
|
7
|
+
TYPES = {
|
8
|
+
typing: 1,
|
9
|
+
message: 2,
|
10
|
+
image: 3,
|
11
|
+
confirm: 4,
|
12
|
+
options: 5,
|
13
|
+
carousel: 6,
|
14
|
+
command: 7,
|
15
|
+
card: 9
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def initialize(type, opts={})
|
19
|
+
@type = type
|
20
|
+
@resource_id = opts[:resource_id]
|
21
|
+
@content = opts[:content]
|
22
|
+
@options = opts[:options]
|
23
|
+
@prefix = opts[:prefix]
|
24
|
+
@text = opts[:text]
|
25
|
+
@preview_url = opts[:preview_url]
|
26
|
+
@resource_url = opts[:resource_url]
|
27
|
+
@from = opts[:from]
|
28
|
+
@to = opts[:to]
|
29
|
+
@channel_id = opts[:channel_id]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require_relative 'asserts'
|
5
|
+
module BotPlatform
|
6
|
+
class Adapter
|
7
|
+
include Asserts
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
|
11
|
+
attr_reader :channels, :channel_map
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@channels = []
|
15
|
+
@channel_map = {}
|
16
|
+
channels = ENV['BOT_CHANNELS']
|
17
|
+
raise 'No BOT_CHANNELS found in environment variables.' if channels.nil? || channels.length == 0
|
18
|
+
channels.split(',').each do |ch|
|
19
|
+
channel = Object.const_get("BotPlatform").const_get("Channels").const_get(ch.capitalize).new
|
20
|
+
@channels << channel
|
21
|
+
@channel_map[ch] = channel
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_activities(turn_context, activities)
|
27
|
+
assert_context_is_not_null turn_context
|
28
|
+
assert_activity_is_not_null activities
|
29
|
+
assert_activity_is_not_null activities[0]
|
30
|
+
|
31
|
+
activities.each do |activity|
|
32
|
+
@channel_map[turn_context.channel_id].send_activity(activity)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_activity(turn_context, activity, cancel_token)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_activity(turn_context, conversation, cancel_token)
|
41
|
+
end
|
42
|
+
|
43
|
+
def continue_conversation(bot_id, bot_cb_handler)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_conversation(bot_id, channel_id, service_url, audience, bot_callback_handler, cancel_token)
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_activity_async(claims_identity, activity, bot_callback_handler, cancel_token)
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_activity(headers, body, &block)
|
53
|
+
channel = nil
|
54
|
+
raise 'No channel registered.' if @channels.nil? || @channels.length==0
|
55
|
+
@channels.each do |ch|
|
56
|
+
if ch.match_request(headers, body)
|
57
|
+
channel = ch
|
58
|
+
break
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
raise 'No channel found' if channel.nil?
|
63
|
+
|
64
|
+
activity = channel.parse_incoming_to_activity(headers, body)
|
65
|
+
|
66
|
+
context = BotPlatform::TurnContext.new self, activity
|
67
|
+
block.call(context)
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_pipeline_async(turn_context, callback, cancel_token)
|
71
|
+
assert_context_is_not_null(turn_context)
|
72
|
+
|
73
|
+
if turn_context.activity != nil
|
74
|
+
# set locale
|
75
|
+
# run middleware
|
76
|
+
# rescue custom onTurnError
|
77
|
+
else
|
78
|
+
unless callback.nil?
|
79
|
+
# proactive
|
80
|
+
callback(turn_context, cancel_token)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Asserts
|
5
|
+
def assert_is_not_empty(param)
|
6
|
+
raise "#{param.name} is empty" if param.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def assert_dialog_id_is_valid(dialog_id)
|
10
|
+
raise "dialog_id is not valid" unless (dialog_id.is_a? String) && !dialog_id.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_dialog_context_is_valid(ctx)
|
14
|
+
raise "dialog context is not valid" unless !ctx.nil? && (ctx.is_a? BotPlatform::Dialogs::DialogContext)
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_prompt_options_is_valid(options)
|
18
|
+
raise "prompt options is not valid" unless !options.nil? && (options.is_a? BotPlatform::Dialogs::Prompts::PromptOptions)
|
19
|
+
end
|
20
|
+
|
21
|
+
def assert_dialog_set_is_valid(dialogs)
|
22
|
+
raise "dialogs is not valid" if dialogs.nil? || !(dialogs.is_a? Dialogs::DialogSet)
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_dialog_state_is_valid(state)
|
26
|
+
raise "dialog state is not valid" if state.nil? || !(state.is_a? Dialogs::DialogState)
|
27
|
+
end
|
28
|
+
|
29
|
+
def assert_dialog_is_valid(dialog)
|
30
|
+
raise "dialog is not valid" if dialog.nil? || !(dialog.is_a? Dialogs::Dialog)
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_dialog_is_uniq(hash, id)
|
34
|
+
raise "dialog is aready added" if !hash[id.to_sym].nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_activity_is_not_null(activity)
|
38
|
+
raise "activity cannot be null" if activity.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
def assert_activity_type_is_not_null(type)
|
42
|
+
raise "activity type cannot be null" if type.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_turn_context_is_valid(turn_context)
|
46
|
+
raise "turn context is not valid" if turn_context.nil? || !(turn_context.is_a? TurnContext)
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_context_is_not_null(turn_context)
|
50
|
+
raise "turn context cannot be null" if turn_context.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_conversation_reference_is_not_null(conversation_ref)
|
54
|
+
raise "conversation reference cannot be null" if conversation_ref.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def assert_activity_list_is_not_null(activities)
|
58
|
+
raise "activity list cannot be null" if activities.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_middleware_is_not_null(middleware)
|
62
|
+
raise "middleware cannot be null" if middleware.nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
def assert_middleware_list_is_not_null(middlewares)
|
66
|
+
raise "middleware list cannot be null" if middlewares.nil?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform
|
4
|
+
module Channels
|
5
|
+
module Base
|
6
|
+
class NeedImplementation < StandardError; end
|
7
|
+
|
8
|
+
def channel_id
|
9
|
+
raise NeedImplementation
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_activity(activity)
|
13
|
+
raise NeedImplementation
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_command(activity)
|
17
|
+
raise NeedImplementation
|
18
|
+
end
|
19
|
+
|
20
|
+
def match_request(headers,body)
|
21
|
+
raise NeedImplementation
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform::Channels
|
4
|
+
class Console
|
5
|
+
include BotPlatform::Channels::Base
|
6
|
+
|
7
|
+
def channel_id
|
8
|
+
"console"
|
9
|
+
end
|
10
|
+
|
11
|
+
def key
|
12
|
+
"X-Bot-Platform-Bot".intern
|
13
|
+
end
|
14
|
+
|
15
|
+
def match_request(headers, body)
|
16
|
+
return false if headers.nil?
|
17
|
+
return !(headers[key].nil? || headers[key].empty?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def send_activity(activity)
|
21
|
+
case activity.type
|
22
|
+
when BotPlatform::Activity::TYPES[:message] then
|
23
|
+
puts "bot> #{activity.text}"
|
24
|
+
when BotPlatform::Activity::TYPES[:carousel] then
|
25
|
+
puts "bot> select from the list:"
|
26
|
+
content = activity.content
|
27
|
+
content[:columns].each_with_index do |col, idx|
|
28
|
+
puts "#{idx+1}: #{col[:title]}(#{col[:text]}) [/#{col[:defaultAction][:data]}]"
|
29
|
+
end
|
30
|
+
when BotPlatform::Activity::TYPES[:options] then
|
31
|
+
puts "bot> #{activity.text}"
|
32
|
+
activity.options.each_with_index{|opt, idx| puts "#{idx+1}: #{opt} [/#{activity.prefix}-opt-#{idx}]"}
|
33
|
+
when BotPlatform::Activity::TYPES[:image] then
|
34
|
+
`open -a '/Applications/Google Chrome.app' #{activity.resource_url}`
|
35
|
+
else
|
36
|
+
puts "bot[debug]> activity.inspect"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_incoming_to_activity(headers, body)
|
41
|
+
user_id = body[:bot_id] || ""
|
42
|
+
room_id = body[:room_id] || ""
|
43
|
+
activity = nil
|
44
|
+
cmd = as_command(headers, body)
|
45
|
+
if cmd
|
46
|
+
activity = BotPlatform::Activity.new ::BotPlatform::Activity::TYPES[:command], {from: {user_id: user_id, room_id: room_id}, text: cmd, channel_id: channel_id}
|
47
|
+
else
|
48
|
+
activity = BotPlatform::Activity.new ::BotPlatform::Activity::TYPES[:message], {from: {user_id: user_id, room_id: room_id}, text: body[:text], channel_id: channel_id}
|
49
|
+
end
|
50
|
+
|
51
|
+
return activity
|
52
|
+
end
|
53
|
+
|
54
|
+
def as_command(headers, body)
|
55
|
+
return body[:cmd] if body[:type] == "cmd_back"
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BotPlatform::Channels
|
4
|
+
class Lineworks
|
5
|
+
include Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@queue = []
|
9
|
+
@api_uri = URI.parse("https://apis.worksmobile.com/r/#{ENV['LINE_API_ID']}/message/v1/bot/#{ENV['BOT_NO']}/message/push")
|
10
|
+
@headers = {
|
11
|
+
"Content-type": "application/json",
|
12
|
+
"consumerKey": ENV['LINE_SERVER_CONSUMER_KEY'],
|
13
|
+
"Authorization": ENV['LINE_SERVER_TOKEN']
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def channel_id
|
18
|
+
"lineworks"
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_incoming_to_activity(headers, body)
|
22
|
+
puts "Headers: #{headers.inspect}"
|
23
|
+
puts "body: #{body.inspect}"
|
24
|
+
user_id = body["source"]["accountId"] || ""
|
25
|
+
room_id = body["source"]["roomId"] || ""
|
26
|
+
activity = nil
|
27
|
+
if cmd = as_command(headers, body)
|
28
|
+
activity = ChatBot::Activity.new ::ChatBot::Activity::TYPES[:command], {from: {user_id: user_id, room_id: room_id}, text: cmd, channel_id: channel_id}
|
29
|
+
elsif body["type"] == "message" && body["content"]["type"] == "image"
|
30
|
+
activity = ChatBot::Activity.new ::ChatBot::Activity::TYPES[:image], {from: {user_id: user_id, room_id: room_id}, resource_id: body["content"]["resourceId"], channel_id: channel_id}
|
31
|
+
else
|
32
|
+
activity = ChatBot::Activity.new ::ChatBot::Activity::TYPES[:message], {from: {user_id: user_id, room_id: room_id}, text: body["content"]["text"], channel_id: channel_id}
|
33
|
+
end
|
34
|
+
puts "parse_incoming_to_activity activity:#{activity.to_json}"
|
35
|
+
|
36
|
+
return activity
|
37
|
+
end
|
38
|
+
|
39
|
+
def send_activity(activity)
|
40
|
+
http = Net::HTTP.new(@api_uri.host, @api_uri.port)
|
41
|
+
http.use_ssl = true
|
42
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
43
|
+
|
44
|
+
content = {}
|
45
|
+
if !activity.to[:room_id].blank?
|
46
|
+
content["roomId"] = activity.to[:room_id]
|
47
|
+
else
|
48
|
+
content["accountId"] = activity.to[:user_id]
|
49
|
+
end
|
50
|
+
case activity.type
|
51
|
+
when ChatBot::Activity::TYPES[:message] then
|
52
|
+
content["content"] = {
|
53
|
+
"type": "text",
|
54
|
+
"text": activity.text
|
55
|
+
}
|
56
|
+
when ChatBot::Activity::TYPES[:image] then
|
57
|
+
content["content"] = {
|
58
|
+
"type": "image",
|
59
|
+
"previewUrl": activity.preview_url,
|
60
|
+
"resourceUrl": activity.resource_url,
|
61
|
+
}
|
62
|
+
when ChatBot::Activity::TYPES[:confirm] then
|
63
|
+
content["content"] = {
|
64
|
+
"type": "button_template",
|
65
|
+
"contentText": activity.text,
|
66
|
+
"actions": [
|
67
|
+
{ "type":"message", "label": "はい", "postback": "#{activity.prefix}-yes"},
|
68
|
+
{ "type":"message", "label": "いいえ","postback":"#{activity.prefix}-no"}
|
69
|
+
]
|
70
|
+
}
|
71
|
+
when ChatBot::Activity::TYPES[:options] then
|
72
|
+
content["content"] = {
|
73
|
+
"type": "button_template",
|
74
|
+
"contentText": activity.text,
|
75
|
+
"actions": activity.options.map{|opt| {"type":"message", "label":opt, "postback": "#{activity.prefix}-opt-#{opt}"}}
|
76
|
+
}
|
77
|
+
when ChatBot::Activity::TYPES[:carousel] then
|
78
|
+
content["content"] = activity.content
|
79
|
+
else
|
80
|
+
end
|
81
|
+
|
82
|
+
http.start do
|
83
|
+
req = Net::HTTP::Post.new(@api_uri.path)
|
84
|
+
req.initialize_http_header(@headers)
|
85
|
+
req.body = content.to_json
|
86
|
+
http.request(req)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def match_request(headers, body)
|
91
|
+
return !headers["X-Works-Signature"].blank?
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def as_command(headers, body)
|
97
|
+
return body["data"] if body["type"] == "postback"
|
98
|
+
return body["content"]["postback"] if body["type"] == "message" && !body["content"]["postback"].blank?
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|