kybus-bot 0.10.0 → 0.11.1
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/lib/kybus/bot/adapters/debug.rb +7 -4
- data/lib/kybus/bot/adapters/telegram.rb +38 -57
- data/lib/kybus/bot/adapters/telegram_file.rb +2 -2
- data/lib/kybus/bot/adapters/telegram_message.rb +7 -3
- data/lib/kybus/bot/base.rb +73 -59
- data/lib/kybus/bot/command/command_definition.rb +2 -0
- data/lib/kybus/bot/command/command_handler.rb +34 -0
- data/lib/kybus/bot/command/command_state.rb +23 -12
- data/lib/kybus/bot/command/command_state_factory.rb +17 -34
- data/lib/kybus/bot/command/execution_context.rb +1 -1
- data/lib/kybus/bot/command/inline_command_matcher.rb +47 -0
- data/lib/kybus/bot/command/parameter_saver.rb +64 -0
- data/lib/kybus/bot/command/regular_command_matcher.rb +47 -0
- data/lib/kybus/bot/command_executor.rb +41 -73
- data/lib/kybus/bot/dsl_methods.rb +7 -3
- data/lib/kybus/bot/exceptions.rb +16 -0
- data/lib/kybus/bot/forkers/base.rb +61 -0
- data/lib/kybus/bot/forkers/lambda_sqs_forker.rb +41 -0
- data/lib/kybus/bot/forkers/thread_forker.rb +18 -0
- data/lib/kybus/bot/migrator.rb +57 -39
- data/lib/kybus/bot/serialized_message.rb +32 -14
- data/lib/kybus/bot/sidekiq_command_executor.rb +2 -0
- data/lib/kybus/bot/version.rb +1 -1
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6027eb3dbf33a839ee3b209eda5c8175a75372a3b03021cf171d07794f9ba1b2
|
4
|
+
data.tar.gz: 1271955a903acbbebecec80eca93302f144bd97ec70b93ad6c2d6171381942a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bee0655c5cc486692a84b0e63cace81b3c0028cd10c88699d486d0e05714acb866f9afc68708b66e1bc3a5c5d94aebc43a04ceb875c99331d7d600faea02d39c
|
7
|
+
data.tar.gz: db7c59187ff02ca0931382d4ad49a9483047dff4ac6c55217aa68ffbcc6c6a9caa5ef4d5416b773e0a51c280e019d6865f359cb70f5689b9b127c6401190e0e7
|
@@ -35,14 +35,17 @@ module Kybus
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
def self.make_message_id
|
39
|
+
@message_id ||= 0
|
40
|
+
@message_id += 1
|
41
|
+
end
|
42
|
+
|
38
43
|
def initialize(text, channel, attachment = nil)
|
39
44
|
super()
|
40
45
|
@text = text
|
41
46
|
@channel = channel
|
42
47
|
@attachment = attachment
|
43
|
-
|
44
|
-
@@message_id += 1
|
45
|
-
@message_id = @@message_id + 1
|
48
|
+
@message_id = DebugMessage.make_message_id
|
46
49
|
end
|
47
50
|
|
48
51
|
# Returns the channel id
|
@@ -125,7 +128,7 @@ module Kybus
|
|
125
128
|
# This adapter is intended to be used on unit tests and development.
|
126
129
|
class Debug
|
127
130
|
# Exception for stoping the loop of messages
|
128
|
-
class NoMoreMessageException < Kybus::Exceptions::
|
131
|
+
class NoMoreMessageException < Kybus::Exceptions::KybusError
|
129
132
|
def initialize
|
130
133
|
super('There are no messages left')
|
131
134
|
end
|
@@ -8,118 +8,70 @@ require_relative 'telegram_message'
|
|
8
8
|
module Kybus
|
9
9
|
module Bot
|
10
10
|
module Adapter
|
11
|
-
##
|
12
|
-
# This adapter is intended to be used on unit tests and development.
|
13
11
|
class Telegram
|
14
12
|
include ::Kybus::Logger
|
15
13
|
|
16
14
|
attr_reader :last_message
|
17
15
|
|
18
|
-
# It receives a hash with the configurations:
|
19
|
-
# - name: the name of the channel
|
20
|
-
# - channels a key value, where the key is a name and the value the
|
21
|
-
# list of the messages in the channel.
|
22
|
-
# - echo: a flag to enable debug messages.
|
23
16
|
def initialize(configs)
|
24
17
|
@config = configs
|
25
18
|
@client = ::Telegram::Bot::Client.new(@config['token'])
|
26
19
|
TelegramFile.register(:cli, @client)
|
27
20
|
end
|
28
21
|
|
29
|
-
# Interface for receiving message
|
30
22
|
def read_message
|
31
|
-
# take the first message from the first open message,
|
32
23
|
loop do
|
33
24
|
@client.listen do |message|
|
34
|
-
log_info('Received message', message: message.to_h,
|
35
|
-
from: message.from.to_h)
|
25
|
+
log_info('Received message', message: message.to_h, from: message.from.to_h)
|
36
26
|
return @last_message = TelegramMessage.new(message)
|
37
27
|
end
|
38
28
|
rescue ::Telegram::Bot::Exceptions::ResponseError => e
|
39
|
-
|
40
|
-
log_error('An error ocurred while calling to Telegram API', e)
|
41
|
-
# :nocov:
|
29
|
+
log_error('An error occurred while calling to Telegram API', e)
|
42
30
|
end
|
43
31
|
end
|
44
32
|
|
45
33
|
def handle_message(body)
|
46
34
|
chat_id = body.dig('message', 'chat', 'id')
|
47
35
|
message_id = body.dig('message', 'message_id')
|
48
|
-
user =
|
36
|
+
user = extract_user(body.dig('message', 'from'))
|
49
37
|
raw_message = body.dig('message', 'text')
|
50
|
-
|
51
|
-
replied_message = body.dig('message', 'reply_to_message')
|
52
38
|
is_private = body.dig('message', 'chat', 'type') == 'private'
|
39
|
+
attachment = extract_attachment(body['message'])
|
40
|
+
serialized_replied_message = serialize_replied_message(body.dig('message', 'reply_to_message'))
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
attachment = if has_attachment
|
58
|
-
body.dig('message',
|
59
|
-
'photo')&.last || body.dig('message', 'document') || body.dig('message', 'video')
|
60
|
-
end
|
61
|
-
|
62
|
-
# Serialize replied_message if it exists
|
63
|
-
serialized_replied_message = if replied_message
|
64
|
-
SerializedMessage.new(
|
65
|
-
provider: 'telegram',
|
66
|
-
channel_id: replied_message.dig('chat', 'id'),
|
67
|
-
message_id: replied_message['message_id'],
|
68
|
-
user: replied_message.dig('from',
|
69
|
-
'username') || replied_message.dig('from',
|
70
|
-
'first_name'),
|
71
|
-
raw_message: replied_message['text'],
|
72
|
-
is_private?: replied_message.dig('chat', 'type') == 'private'
|
73
|
-
).serialize
|
74
|
-
end
|
75
|
-
|
76
|
-
SerializedMessage.new(
|
77
|
-
provider: 'telegram',
|
78
|
-
channel_id: chat_id,
|
79
|
-
message_id:,
|
80
|
-
user:,
|
81
|
-
replied_message: serialized_replied_message,
|
82
|
-
raw_message:,
|
83
|
-
is_private?: is_private,
|
84
|
-
attachment:
|
85
|
-
)
|
42
|
+
SerializedMessage.new(provider: 'telegram', channel_id: chat_id, message_id:, user:,
|
43
|
+
replied_message: serialized_replied_message, raw_message:, is_private?: is_private,
|
44
|
+
attachment:)
|
86
45
|
end
|
87
46
|
|
88
47
|
def mention(id)
|
89
48
|
"[user](tg://user?id=#{id})"
|
90
49
|
end
|
91
50
|
|
92
|
-
# interface for sending messages
|
93
51
|
def send_message(contents, channel_name)
|
94
52
|
log_debug('Sending message', channel_name:, message: contents)
|
95
53
|
@client.api.send_message(chat_id: channel_name.to_i, text: contents, parse_mode: @config['parse_mode'])
|
96
|
-
# :nocov:
|
97
54
|
rescue ::Telegram::Bot::Exceptions::ResponseError => e
|
98
55
|
nil if e.error_code == '403'
|
99
56
|
end
|
100
|
-
# :nocov:
|
101
57
|
|
102
|
-
# interface for sending video
|
103
58
|
def send_video(channel_name, video_url, comment = nil)
|
104
59
|
file = Faraday::FilePart.new(video_url, 'video/mp4')
|
105
60
|
@client.api.send_video(chat_id: channel_name, video: file, caption: comment)
|
106
61
|
end
|
107
62
|
|
108
|
-
# interface for sending uadio
|
109
63
|
def send_audio(channel_name, audio_url)
|
110
64
|
file = Faraday::FilePart.new(audio_url, 'audio/mp3')
|
111
65
|
@client.api.send_audio(chat_id: channel_name, audio: file)
|
112
66
|
end
|
113
67
|
|
114
|
-
# interface for sending image
|
115
68
|
def send_image(channel_name, image_url, comment = nil)
|
116
69
|
file = Faraday::FilePart.new(image_url, 'image/jpeg')
|
117
70
|
@client.api.send_photo(chat_id: channel_name, photo: file, caption: comment)
|
118
71
|
end
|
119
72
|
|
120
|
-
# interface for sending document
|
121
73
|
def send_document(channel_name, image_url)
|
122
|
-
file = Faraday::FilePart.new(image_url, 'application/
|
74
|
+
file = Faraday::FilePart.new(image_url, 'application/octet-stream')
|
123
75
|
@client.api.send_document(chat_id: channel_name, document: file)
|
124
76
|
end
|
125
77
|
|
@@ -130,6 +82,35 @@ module Kybus
|
|
130
82
|
def file_builder(file)
|
131
83
|
TelegramFile.new(file)
|
132
84
|
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_user(from)
|
89
|
+
from['username'] || from['first_name']
|
90
|
+
end
|
91
|
+
|
92
|
+
def extract_attachment(message)
|
93
|
+
return unless message
|
94
|
+
|
95
|
+
%w[photo document video].each do |type|
|
96
|
+
attachment = message[type]
|
97
|
+
return type == 'photo' ? attachment.last : attachment if attachment
|
98
|
+
end
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def serialize_replied_message(replied_message)
|
103
|
+
return unless replied_message
|
104
|
+
|
105
|
+
SerializedMessage.new(
|
106
|
+
provider: 'telegram',
|
107
|
+
channel_id: replied_message.dig('chat', 'id'),
|
108
|
+
message_id: replied_message['message_id'],
|
109
|
+
user: extract_user(replied_message['from']),
|
110
|
+
raw_message: replied_message['text'],
|
111
|
+
is_private?: replied_message.dig('chat', 'type') == 'private'
|
112
|
+
).serialize
|
113
|
+
end
|
133
114
|
end
|
134
115
|
|
135
116
|
register('telegram', Telegram)
|
@@ -40,7 +40,7 @@ module Kybus
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def original_name
|
43
|
-
meta.
|
43
|
+
meta.file_path
|
44
44
|
end
|
45
45
|
|
46
46
|
def file_name
|
@@ -49,7 +49,7 @@ module Kybus
|
|
49
49
|
|
50
50
|
def download
|
51
51
|
token = cli.api.token
|
52
|
-
file_path = meta.
|
52
|
+
file_path = meta.file_path
|
53
53
|
path = "https://api.telegram.org/file/bot#{token}/#{file_path}"
|
54
54
|
Faraday.get(path).body
|
55
55
|
end
|
@@ -47,9 +47,13 @@ module Kybus
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def attachment
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
%i[document photo audio].each do |method|
|
51
|
+
next unless @message.respond_to?(method)
|
52
|
+
|
53
|
+
attachment = @message.public_send(method)
|
54
|
+
return method == :photo ? attachment.last : attachment if attachment
|
55
|
+
end
|
56
|
+
nil
|
53
57
|
end
|
54
58
|
|
55
59
|
def user
|
data/lib/kybus/bot/base.rb
CHANGED
@@ -6,25 +6,19 @@ require 'kybus/storage'
|
|
6
6
|
require_relative 'command/command_state'
|
7
7
|
require_relative 'dsl_methods'
|
8
8
|
require_relative 'command_executor'
|
9
|
+
require_relative 'command/command_definition'
|
10
|
+
require_relative 'command/execution_context'
|
9
11
|
require_relative 'command/command_state_factory'
|
10
|
-
|
12
|
+
require_relative 'exceptions'
|
13
|
+
require_relative 'forkers/base'
|
14
|
+
require_relative 'forkers/thread_forker'
|
15
|
+
require_relative 'forkers/lambda_sqs_forker'
|
11
16
|
require 'kybus/logger'
|
12
17
|
require 'forwardable'
|
13
18
|
|
14
19
|
module Kybus
|
15
20
|
module Bot
|
16
|
-
|
17
|
-
# provider and the state storage inside an object.
|
18
|
-
class Base
|
19
|
-
class BotError < StandardError; end
|
20
|
-
class AbortError < BotError; end
|
21
|
-
|
22
|
-
class EmptyMessageError < BotError
|
23
|
-
def initialize
|
24
|
-
super('Message is empty')
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
21
|
+
class Base # rubocop: disable Metrics/ClassLength
|
28
22
|
extend Forwardable
|
29
23
|
include Kybus::Logger
|
30
24
|
|
@@ -44,41 +38,17 @@ module Kybus
|
|
44
38
|
def_delegators :executor, :state, :precommand_hook
|
45
39
|
def_delegators :definitions, :registered_commands
|
46
40
|
|
47
|
-
# Configurations needed:
|
48
|
-
# - pool_size: number of threads created in execution
|
49
|
-
# - provider: a configuration for a thread provider.
|
50
|
-
# See supported adapters
|
51
|
-
# - name: The bot name
|
52
|
-
# - repository: Configurations about the state storage
|
53
|
-
|
54
41
|
def initialize(configs)
|
55
|
-
|
42
|
+
@pool_size = configs['pool_size']
|
56
43
|
@provider = Kybus::Bot::Adapter.from_config(configs['provider'])
|
57
|
-
# TODO: move this to config
|
58
|
-
repository = make_repository(configs)
|
59
44
|
@definitions = Kybus::Bot::CommandDefinition.new
|
45
|
+
repository = create_repository(configs)
|
60
46
|
command_factory = CommandStateFactory.new(repository, @definitions)
|
61
|
-
@executor =
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
register_command('default') { nil }
|
68
|
-
rescue_from(::Kybus::Bot::Base::AbortError) do
|
69
|
-
msg = params[:_last_exception]&.message
|
70
|
-
send_message(msg) if msg && msg != 'Kybus::Bot::Base::AbortError'
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def make_repository(configs)
|
75
|
-
repository_config = configs['state_repository'].merge('primary_key' => 'channel_id', 'table' => 'bot_sessions')
|
76
|
-
repository_config.merge!('fields' => DYNAMOID_FIELDS) if repository_config['name'] == 'dynamoid'
|
77
|
-
Kybus::Storage::Repository.from_config(nil, repository_config, {})
|
78
|
-
end
|
79
|
-
|
80
|
-
def extend(*args)
|
81
|
-
DSLMethods.include(*args)
|
47
|
+
@executor = create_executor(configs, command_factory)
|
48
|
+
register_default_command
|
49
|
+
register_abort_handler
|
50
|
+
build_forker(configs)
|
51
|
+
build_pool
|
82
52
|
end
|
83
53
|
|
84
54
|
def self.helpers(mod = nil, &)
|
@@ -86,14 +56,8 @@ module Kybus
|
|
86
56
|
DSLMethods.class_eval(&) if block_given?
|
87
57
|
end
|
88
58
|
|
89
|
-
def
|
90
|
-
|
91
|
-
# TODO: Create a subclass with the context execution
|
92
|
-
Kybus::DRY::Daemon.new(pool_size, true) do
|
93
|
-
message = provider.read_message
|
94
|
-
executor.process_message(message)
|
95
|
-
end
|
96
|
-
end
|
59
|
+
def extend(*)
|
60
|
+
DSLMethods.include(*)
|
97
61
|
end
|
98
62
|
|
99
63
|
def dsl
|
@@ -105,13 +69,14 @@ module Kybus
|
|
105
69
|
@executor.process_message(parsed)
|
106
70
|
end
|
107
71
|
|
108
|
-
|
72
|
+
def handle_job(job, args, channel_id)
|
73
|
+
@executor.execution_context.load_state!(channel_id)
|
74
|
+
@forker.handle_job(job, args)
|
75
|
+
end
|
76
|
+
|
109
77
|
def run
|
110
|
-
# TODO: Implement an interface for killing the process
|
111
78
|
pool.each(&:run)
|
112
|
-
# :nocov: #
|
113
79
|
pool.each(&:await)
|
114
|
-
# :nocov: #
|
115
80
|
end
|
116
81
|
|
117
82
|
def redirect(command, *params)
|
@@ -127,14 +92,63 @@ module Kybus
|
|
127
92
|
definitions.register_command(klass, params, &)
|
128
93
|
end
|
129
94
|
|
95
|
+
def register_job(name, args, &)
|
96
|
+
@forker.register_command(name, args, &)
|
97
|
+
end
|
98
|
+
|
99
|
+
def invoke_job(name, args)
|
100
|
+
@forker.fork(name, args, dsl)
|
101
|
+
end
|
102
|
+
|
130
103
|
def rescue_from(klass, &)
|
131
104
|
definitions.register_command(klass, [], &)
|
132
105
|
end
|
133
106
|
|
134
|
-
def method_missing(method, ...)
|
135
|
-
|
107
|
+
def method_missing(method, ...) # rubocop: disable Style/MissingRespondToMissing
|
108
|
+
return dsl.send(method, ...) if dsl.respond_to?(method)
|
136
109
|
|
137
|
-
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def create_repository(configs)
|
116
|
+
repository_config = configs['state_repository'].merge('primary_key' => 'channel_id', 'table' => 'bot_sessions')
|
117
|
+
repository_config.merge!('fields' => DYNAMOID_FIELDS) if repository_config['name'] == 'dynamoid'
|
118
|
+
Kybus::Storage::Repository.from_config(nil, repository_config, {})
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_executor(configs, command_factory)
|
122
|
+
if configs['sidekiq']
|
123
|
+
require_relative 'sidekiq_command_executor'
|
124
|
+
Kybus::Bot::SidekiqCommandExecutor.new(self, command_factory, configs)
|
125
|
+
else
|
126
|
+
Kybus::Bot::CommandExecutor.new(self, command_factory, configs['inline_args'])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def register_default_command
|
131
|
+
register_command('default') { nil }
|
132
|
+
end
|
133
|
+
|
134
|
+
def register_abort_handler
|
135
|
+
rescue_from(Kybus::Bot::Base::AbortError) do
|
136
|
+
msg = params[:_last_exception]&.message
|
137
|
+
send_message(msg) if msg && msg != 'Kybus::Bot::Base::AbortError'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_forker(configs)
|
142
|
+
@forker = Forkers.from_config(self, configs['forker'])
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_pool
|
146
|
+
@pool = Array.new(pool_size) do
|
147
|
+
Kybus::DRY::Daemon.new(pool_size, true) do
|
148
|
+
message = provider.read_message
|
149
|
+
executor.process_message(message)
|
150
|
+
end
|
151
|
+
end
|
138
152
|
end
|
139
153
|
end
|
140
154
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
class CommandHandler
|
6
|
+
def initialize(executor)
|
7
|
+
@executor = executor
|
8
|
+
end
|
9
|
+
|
10
|
+
def run_command_or_prepare!
|
11
|
+
if @executor.execution_context.ready?
|
12
|
+
run_ready_command
|
13
|
+
else
|
14
|
+
ask_for_next_param
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def run_ready_command
|
21
|
+
@executor.dsl.state = @executor.execution_context.state
|
22
|
+
@executor.dsl.instance_eval(&@executor.precommand_hook)
|
23
|
+
msg = @executor.run_command!
|
24
|
+
@executor.execution_context.clear_command
|
25
|
+
msg
|
26
|
+
end
|
27
|
+
|
28
|
+
def ask_for_next_param
|
29
|
+
param = @executor.execution_context.next_missing_param
|
30
|
+
@executor.ask_param(param, @executor.execution_context.state.command.params_ask_label(param))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -7,12 +7,7 @@ module Kybus
|
|
7
7
|
|
8
8
|
def initialize(data, command)
|
9
9
|
@command = command
|
10
|
-
data =
|
11
|
-
(data[:params] = JSON.parse(data[:params] || '{}', symbolize_names: true)) if data[:params].is_a?(String)
|
12
|
-
(data[:metadata] = JSON.parse(data[:metadata] || '{}', symbolize_names: true)) if data[:metadata].is_a?(String)
|
13
|
-
(data[:files] = JSON.parse(data[:files] || '{}', symbolize_names: true)) if data[:files].is_a?(String)
|
14
|
-
(data[:last_message] = data[:last_message] && SerializedMessage.from_json(data[:last_message]))
|
15
|
-
@data = data
|
10
|
+
@data = parse_data(data)
|
16
11
|
end
|
17
12
|
|
18
13
|
def self.from_json(str, commands_provider)
|
@@ -36,12 +31,11 @@ module Kybus
|
|
36
31
|
command&.ready?(params)
|
37
32
|
end
|
38
33
|
|
39
|
-
# validates which is the following parameter required
|
40
34
|
def next_missing_param
|
41
35
|
command.next_missing_param(params)
|
42
36
|
end
|
43
37
|
|
44
|
-
def
|
38
|
+
def last_message=(msg)
|
45
39
|
@data[:last_message] = msg
|
46
40
|
end
|
47
41
|
|
@@ -91,12 +85,29 @@ module Kybus
|
|
91
85
|
|
92
86
|
def save!
|
93
87
|
backup = @data.clone
|
94
|
-
|
95
|
-
@data[param] = @data[param].to_json
|
96
|
-
end
|
88
|
+
serialize_data!
|
97
89
|
@data.store
|
90
|
+
@data = backup
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def parse_data(data)
|
96
|
+
data = JSON.parse(data, symbolize_names: true) if data.is_a?(String)
|
97
|
+
%i[params metadata files].each do |key|
|
98
|
+
data[key] = parse_json(data[key]) if data[key].is_a?(String)
|
99
|
+
end
|
100
|
+
data[:last_message] = SerializedMessage.from_json(data[:last_message]) if data[:last_message]
|
101
|
+
data
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_json(value)
|
105
|
+
JSON.parse(value || '{}', symbolize_names: true)
|
106
|
+
end
|
107
|
+
|
108
|
+
def serialize_data!
|
98
109
|
%i[params files last_message metadata].each do |param|
|
99
|
-
@data[param] =
|
110
|
+
@data[param] = @data[param].to_json
|
100
111
|
end
|
101
112
|
end
|
102
113
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'regular_command_matcher'
|
4
|
+
require_relative 'inline_command_matcher'
|
5
|
+
|
3
6
|
module Kybus
|
4
7
|
module Bot
|
5
8
|
class CommandStateFactory
|
@@ -7,29 +10,14 @@ module Kybus
|
|
7
10
|
attr_reader :factory
|
8
11
|
|
9
12
|
def initialize(repository, definitions)
|
10
|
-
factory =
|
11
|
-
factory.register(:default, :json)
|
12
|
-
factory.register(:json, repository)
|
13
|
-
@factory = factory
|
13
|
+
@factory = build_factory(repository)
|
14
14
|
@definitions = definitions
|
15
|
+
@regular_command_matcher = RegularCommandMatcher.new(definitions)
|
16
|
+
@inline_command_matcher = InlineCommandMatcher.new(definitions)
|
15
17
|
end
|
16
18
|
|
17
19
|
def command(search)
|
18
|
-
@
|
19
|
-
case name
|
20
|
-
when String
|
21
|
-
return command if name == search
|
22
|
-
when Class
|
23
|
-
return command if search.is_a?(name)
|
24
|
-
when Regexp
|
25
|
-
if search.is_a?(String) && name.match?(search)
|
26
|
-
storable_command = command.clone
|
27
|
-
storable_command.name = search
|
28
|
-
return storable_command
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
nil
|
20
|
+
@regular_command_matcher.find_command(search)
|
33
21
|
end
|
34
22
|
|
35
23
|
def default_command
|
@@ -41,21 +29,7 @@ module Kybus
|
|
41
29
|
end
|
42
30
|
|
43
31
|
def command_with_inline_arg(name_with_arg)
|
44
|
-
@
|
45
|
-
case name
|
46
|
-
when Class
|
47
|
-
return [command, []] if name_with_arg.is_a?(name)
|
48
|
-
when String
|
49
|
-
return [command, name_with_arg.gsub(name, '').split('__')] if name_with_arg.start_with?(name)
|
50
|
-
when Regexp
|
51
|
-
next unless name_with_arg.match?(name)
|
52
|
-
|
53
|
-
storable_command = command.dup
|
54
|
-
storable_command.name = name_with_arg
|
55
|
-
return [storable_command, [name_with_arg]]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
nil
|
32
|
+
@inline_command_matcher.find_command_with_inline_arg(name_with_arg)
|
59
33
|
end
|
60
34
|
|
61
35
|
def load_state(channel)
|
@@ -64,6 +38,15 @@ module Kybus
|
|
64
38
|
rescue Kybus::Storage::Exceptions::ObjectNotFound
|
65
39
|
CommandState.new(factory.create(channel_id: channel.to_s, params: '{}', metadata: '{}', last_message: nil), nil)
|
66
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def build_factory(repository)
|
45
|
+
factory = Kybus::Storage::Factory.new(EmptyModel)
|
46
|
+
factory.register(:default, :json)
|
47
|
+
factory.register(:json, repository)
|
48
|
+
factory
|
49
|
+
end
|
67
50
|
end
|
68
51
|
end
|
69
52
|
end
|
@@ -9,7 +9,7 @@ module Kybus
|
|
9
9
|
attr_reader :state
|
10
10
|
|
11
11
|
def_delegator :state, :requested_param=, :next_param=
|
12
|
-
def_delegators :state, :clear_command, :save!, :ready?, :next_missing_param, :
|
12
|
+
def_delegators :state, :clear_command, :save!, :ready?, :next_missing_param, :last_message=
|
13
13
|
|
14
14
|
def block
|
15
15
|
state.command.block
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
class InlineCommandMatcher
|
6
|
+
def initialize(definitions)
|
7
|
+
@definitions = definitions
|
8
|
+
@matchers = build_matchers
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_command_with_inline_arg(name_with_arg)
|
12
|
+
@definitions.each do |name, command|
|
13
|
+
matcher = @matchers[name.class]
|
14
|
+
result = matcher&.call(name, command, name_with_arg)
|
15
|
+
return result if result
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build_matchers
|
23
|
+
{
|
24
|
+
Class => method(:match_inline_class),
|
25
|
+
String => method(:match_inline_string),
|
26
|
+
Regexp => method(:match_inline_regexp)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def match_inline_class(name, command, name_with_arg)
|
31
|
+
[command, []] if name_with_arg.is_a?(name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def match_inline_string(name, command, name_with_arg)
|
35
|
+
[command, name_with_arg.gsub(name, '').split('__')] if name_with_arg.start_with?(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def match_inline_regexp(name, command, name_with_arg)
|
39
|
+
return unless name_with_arg.match?(name)
|
40
|
+
|
41
|
+
storable_command = command.dup
|
42
|
+
storable_command.name = name_with_arg
|
43
|
+
[storable_command, [name_with_arg]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|