kybus-bot 0.10.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|