kybus-bot 0.8.2 → 0.10.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/lib/kybus/bot/adapters/debug.rb +5 -1
- data/lib/kybus/bot/adapters/discord.rb +35 -19
- data/lib/kybus/bot/adapters/telegram.rb +45 -2
- data/lib/kybus/bot/base.rb +55 -11
- data/lib/kybus/bot/command/command_definition.rb +4 -4
- data/lib/kybus/bot/command/command_state.rb +33 -5
- data/lib/kybus/bot/command/command_state_factory.rb +1 -1
- data/lib/kybus/bot/command/execution_context.rb +2 -2
- data/lib/kybus/bot/command_executor.rb +19 -11
- data/lib/kybus/bot/dsl_methods.rb +16 -6
- data/lib/kybus/bot/message.rb +15 -1
- data/lib/kybus/bot/migrator.rb +42 -3
- data/lib/kybus/bot/serialized_message.rb +48 -0
- data/lib/kybus/bot/sidekiq_command_executor.rb +54 -0
- data/lib/kybus/bot/sidekiq_dsl_methods.rb +24 -0
- data/lib/kybus/bot/test.rb +1 -1
- data/lib/kybus/bot/version.rb +1 -1
- metadata +6 -185
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5133b5a89dc9947680ecdbd8ec25befed895f57e017d19121278617b6bc0ff4d
|
4
|
+
data.tar.gz: '08e309bdf344e7066a5354c516487b12764b85cca7ea38165054471e5280cba7'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da4828437e972ecd01dab962800ae67f011a7ed8ceb88b9f1484d7c4caef740811dfd816f578620a5679a86768b21a00a976750d206c25cdcd7df66e4e3f4a6c
|
7
|
+
data.tar.gz: c7bc12f54fb1352f774cbf607c12bbcae4802edc0d0058e99a38a7bb1554a8083b721a4e715e2185fd92fab668ece278e83ce4a2a15d43f28ddd23f5175cccfa
|
@@ -70,6 +70,10 @@ module Kybus
|
|
70
70
|
def is_private?
|
71
71
|
true
|
72
72
|
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
@text
|
76
|
+
end
|
73
77
|
end
|
74
78
|
|
75
79
|
# This class simulates a message chat with a user.
|
@@ -205,7 +209,7 @@ module Kybus
|
|
205
209
|
|
206
210
|
# changes echo config
|
207
211
|
def echo=(toogle)
|
208
|
-
@channels.
|
212
|
+
@channels.each_value { |channel| channel.echo = toogle }
|
209
213
|
end
|
210
214
|
end
|
211
215
|
|
@@ -11,7 +11,7 @@ module Kybus
|
|
11
11
|
class DiscordMessage < Kybus::Bot::Message
|
12
12
|
# It receives a string with the raw text and the id of the channel
|
13
13
|
def initialize(msg)
|
14
|
-
super
|
14
|
+
super()
|
15
15
|
@message = msg
|
16
16
|
end
|
17
17
|
|
@@ -20,6 +20,18 @@ module Kybus
|
|
20
20
|
@message.channel.id
|
21
21
|
end
|
22
22
|
|
23
|
+
def message_id
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_attachment?
|
28
|
+
!!attachment
|
29
|
+
end
|
30
|
+
|
31
|
+
def attachment
|
32
|
+
@message.file
|
33
|
+
end
|
34
|
+
|
23
35
|
# Returns the message contents
|
24
36
|
def raw_message
|
25
37
|
@message.content
|
@@ -80,7 +92,7 @@ module Kybus
|
|
80
92
|
end
|
81
93
|
|
82
94
|
# interface for sending messages
|
83
|
-
def send_message(
|
95
|
+
def send_message(contents, channel_name, _caption = nil)
|
84
96
|
puts "#{channel_name} => #{contents}" if @config['debug']
|
85
97
|
channel = @client.channel(channel_name)
|
86
98
|
if channel
|
@@ -90,23 +102,27 @@ module Kybus
|
|
90
102
|
end
|
91
103
|
end
|
92
104
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
def message_builder(raw_message)
|
106
|
+
DiscordMessage.new(raw_message)
|
107
|
+
end
|
108
|
+
|
109
|
+
def send_file(channel_name, file, _caption = nil)
|
110
|
+
@client.send_file(channel_name, File.open(file, 'r'))
|
111
|
+
end
|
112
|
+
|
113
|
+
def send_video(channel_name, file, _caption = nil)
|
114
|
+
@client.send_file(channel_name, File.open(file, 'r'))
|
115
|
+
end
|
116
|
+
|
117
|
+
# interface for sending uadio
|
118
|
+
def send_audio(channel_name, file, _caption = nil)
|
119
|
+
@client.send_file(channel_name, File.open(file, 'r'))
|
120
|
+
end
|
121
|
+
|
122
|
+
# interface for sending image
|
123
|
+
def send_image(channel_name, file, _caption = nil)
|
124
|
+
@client.send_file(channel_name, File.open(file, 'r'))
|
125
|
+
end
|
110
126
|
end
|
111
127
|
|
112
128
|
register('discord', Discord)
|
@@ -42,17 +42,60 @@ module Kybus
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def handle_message(body)
|
46
|
+
chat_id = body.dig('message', 'chat', 'id')
|
47
|
+
message_id = body.dig('message', 'message_id')
|
48
|
+
user = body.dig('message', 'from', 'username') || body.dig('message', 'from', 'first_name')
|
49
|
+
raw_message = body.dig('message', 'text')
|
50
|
+
|
51
|
+
replied_message = body.dig('message', 'reply_to_message')
|
52
|
+
is_private = body.dig('message', 'chat', 'type') == 'private'
|
53
|
+
|
54
|
+
# Check if the message has an attachment
|
55
|
+
has_attachment = body.dig('message',
|
56
|
+
'photo') || body.dig('message', 'document') || body.dig('message', 'video')
|
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
|
+
)
|
86
|
+
end
|
87
|
+
|
45
88
|
def mention(id)
|
46
89
|
"[user](tg://user?id=#{id})"
|
47
90
|
end
|
48
91
|
|
49
92
|
# interface for sending messages
|
50
93
|
def send_message(contents, channel_name)
|
51
|
-
|
94
|
+
log_debug('Sending message', channel_name:, message: contents)
|
52
95
|
@client.api.send_message(chat_id: channel_name.to_i, text: contents, parse_mode: @config['parse_mode'])
|
53
96
|
# :nocov:
|
54
97
|
rescue ::Telegram::Bot::Exceptions::ResponseError => e
|
55
|
-
|
98
|
+
nil if e.error_code == '403'
|
56
99
|
end
|
57
100
|
# :nocov:
|
58
101
|
|
data/lib/kybus/bot/base.rb
CHANGED
@@ -17,6 +17,7 @@ module Kybus
|
|
17
17
|
# provider and the state storage inside an object.
|
18
18
|
class Base
|
19
19
|
class BotError < StandardError; end
|
20
|
+
class AbortError < BotError; end
|
20
21
|
|
21
22
|
class EmptyMessageError < BotError
|
22
23
|
def initialize
|
@@ -29,6 +30,17 @@ module Kybus
|
|
29
30
|
|
30
31
|
attr_reader :provider, :executor, :pool_size, :pool, :definitions
|
31
32
|
|
33
|
+
DYNAMOID_FIELDS = {
|
34
|
+
'channel_id' => :string,
|
35
|
+
'user' => :string,
|
36
|
+
'params' => :string,
|
37
|
+
'metadata' => :string,
|
38
|
+
'files' => :string,
|
39
|
+
'cmd' => :string,
|
40
|
+
'requested_param' => :string,
|
41
|
+
'last_message' => :string
|
42
|
+
}.freeze
|
43
|
+
|
32
44
|
def_delegators :executor, :state, :precommand_hook
|
33
45
|
def_delegators :definitions, :registered_commands
|
34
46
|
|
@@ -38,19 +50,40 @@ module Kybus
|
|
38
50
|
# See supported adapters
|
39
51
|
# - name: The bot name
|
40
52
|
# - repository: Configurations about the state storage
|
53
|
+
|
41
54
|
def initialize(configs)
|
42
55
|
build_pool(configs['pool_size'])
|
43
56
|
@provider = Kybus::Bot::Adapter.from_config(configs['provider'])
|
44
57
|
# TODO: move this to config
|
45
|
-
repository =
|
46
|
-
nil,
|
47
|
-
configs['state_repository'].merge('primary_key' => 'channel_id', 'table' => 'bot_sessions'),
|
48
|
-
{}
|
49
|
-
)
|
58
|
+
repository = make_repository(configs)
|
50
59
|
@definitions = Kybus::Bot::CommandDefinition.new
|
51
60
|
command_factory = CommandStateFactory.new(repository, @definitions)
|
52
|
-
@executor =
|
61
|
+
@executor = if configs['sidekiq']
|
62
|
+
require_relative 'sidekiq_command_executor'
|
63
|
+
Kybus::Bot::SidekiqCommandExecutor.new(self, command_factory, configs)
|
64
|
+
else
|
65
|
+
Kybus::Bot::CommandExecutor.new(self, command_factory, configs['inline_args'])
|
66
|
+
end
|
53
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)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.helpers(mod = nil, &)
|
85
|
+
DSLMethods.include(mod) if mod
|
86
|
+
DSLMethods.class_eval(&) if block_given?
|
54
87
|
end
|
55
88
|
|
56
89
|
def build_pool(pool_size)
|
@@ -67,6 +100,11 @@ module Kybus
|
|
67
100
|
@executor.dsl
|
68
101
|
end
|
69
102
|
|
103
|
+
def handle_message(msg)
|
104
|
+
parsed = @provider.handle_message(msg)
|
105
|
+
@executor.process_message(parsed)
|
106
|
+
end
|
107
|
+
|
70
108
|
# Starts the bot execution, this is a blocking call.
|
71
109
|
def run
|
72
110
|
# TODO: Implement an interface for killing the process
|
@@ -77,7 +115,7 @@ module Kybus
|
|
77
115
|
end
|
78
116
|
|
79
117
|
def redirect(command, *params)
|
80
|
-
@executor.
|
118
|
+
@executor.redirect(command, params)
|
81
119
|
end
|
82
120
|
|
83
121
|
def send_message(contents, channel)
|
@@ -85,12 +123,18 @@ module Kybus
|
|
85
123
|
provider.message_builder(@provider.send_message(contents, channel))
|
86
124
|
end
|
87
125
|
|
88
|
-
def register_command(klass, params = [], &
|
89
|
-
definitions.register_command(klass, params, &
|
126
|
+
def register_command(klass, params = [], &)
|
127
|
+
definitions.register_command(klass, params, &)
|
90
128
|
end
|
91
129
|
|
92
|
-
def rescue_from(klass, &
|
93
|
-
definitions.register_command(klass, [], &
|
130
|
+
def rescue_from(klass, &)
|
131
|
+
definitions.register_command(klass, [], &)
|
132
|
+
end
|
133
|
+
|
134
|
+
def method_missing(method, ...)
|
135
|
+
raise unless dsl.respond_to?(method)
|
136
|
+
|
137
|
+
dsl.send(method, ...)
|
94
138
|
end
|
95
139
|
end
|
96
140
|
end
|
@@ -8,16 +8,16 @@ module Kybus
|
|
8
8
|
end
|
9
9
|
|
10
10
|
# Stores an operation definition
|
11
|
-
def register_command(name, params, &
|
12
|
-
@commands[name] = Command.new(name, params, &
|
11
|
+
def register_command(name, params, &)
|
12
|
+
@commands[name] = Command.new(name, params, &)
|
13
13
|
end
|
14
14
|
|
15
15
|
def registered_commands
|
16
16
|
@commands.keys
|
17
17
|
end
|
18
18
|
|
19
|
-
def each(&
|
20
|
-
@commands.each(&
|
19
|
+
def each(&)
|
20
|
+
@commands.each(&)
|
21
21
|
end
|
22
22
|
|
23
23
|
# Returns a command with the name
|
@@ -7,11 +7,27 @@ module Kybus
|
|
7
7
|
|
8
8
|
def initialize(data, command)
|
9
9
|
@command = command
|
10
|
-
data
|
11
|
-
data[:
|
10
|
+
data = JSON.parse(data, symbolize_names: true) if data.is_a?(String)
|
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]))
|
12
15
|
@data = data
|
13
16
|
end
|
14
17
|
|
18
|
+
def self.from_json(str, commands_provider)
|
19
|
+
data = JSON.parse(str, symbolize_names: true)
|
20
|
+
new(data[:data], commands_provider.command(data[:command]))
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(*_args)
|
24
|
+
to_h.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
{ command: command.name, data: @data.to_h.merge(last_message: @data[:last_message]&.to_h) }
|
29
|
+
end
|
30
|
+
|
15
31
|
def clear_command
|
16
32
|
@data[:cmd] = nil
|
17
33
|
end
|
@@ -25,6 +41,14 @@ module Kybus
|
|
25
41
|
command.next_missing_param(params)
|
26
42
|
end
|
27
43
|
|
44
|
+
def set_last_message(msg)
|
45
|
+
@data[:last_message] = msg
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_message
|
49
|
+
@data[:last_message]
|
50
|
+
end
|
51
|
+
|
28
52
|
def command=(cmd)
|
29
53
|
@command = cmd
|
30
54
|
@data[:cmd] = cmd.name
|
@@ -46,7 +70,7 @@ module Kybus
|
|
46
70
|
|
47
71
|
def save_file(identifier, file)
|
48
72
|
files[identifier] = file
|
49
|
-
store_param("_#{@data[:requested_param]}_filename"
|
73
|
+
store_param(:"_#{@data[:requested_param]}_filename", file.file_name)
|
50
74
|
end
|
51
75
|
|
52
76
|
def requested_param=(param)
|
@@ -61,13 +85,17 @@ module Kybus
|
|
61
85
|
@data[:params][param] = value
|
62
86
|
end
|
63
87
|
|
88
|
+
def metadata
|
89
|
+
@data[:metadata] || {}
|
90
|
+
end
|
91
|
+
|
64
92
|
def save!
|
65
93
|
backup = @data.clone
|
66
|
-
%i[params files].each do |param|
|
94
|
+
%i[params files last_message metadata].each do |param|
|
67
95
|
@data[param] = @data[param].to_json
|
68
96
|
end
|
69
97
|
@data.store
|
70
|
-
%i[params files].each do |param|
|
98
|
+
%i[params files last_message metadata].each do |param|
|
71
99
|
@data[param] = backup[param]
|
72
100
|
end
|
73
101
|
end
|
@@ -62,7 +62,7 @@ module Kybus
|
|
62
62
|
data = factory.get(channel.to_s)
|
63
63
|
CommandState.new(data, command(data[:cmd]))
|
64
64
|
rescue Kybus::Storage::Exceptions::ObjectNotFound
|
65
|
-
CommandState.new(factory.create(channel_id: channel.to_s, params: '{}'), nil)
|
65
|
+
CommandState.new(factory.create(channel_id: channel.to_s, params: '{}', metadata: '{}', last_message: nil), nil)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
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, :set_last_message
|
13
13
|
|
14
14
|
def block
|
15
15
|
state.command.block
|
@@ -55,7 +55,7 @@ module Kybus
|
|
55
55
|
|
56
56
|
# stores the command into state
|
57
57
|
def command=(cmd)
|
58
|
-
log_debug('Message set as command', command: cmd)
|
58
|
+
log_debug('Message set as command', command: cmd.name)
|
59
59
|
state.command = cmd
|
60
60
|
end
|
61
61
|
end
|
@@ -19,6 +19,10 @@ module Kybus
|
|
19
19
|
execution_context&.state
|
20
20
|
end
|
21
21
|
|
22
|
+
def last_message
|
23
|
+
state&.last_message
|
24
|
+
end
|
25
|
+
|
22
26
|
def initialize(bot, channel_factory, inline_args)
|
23
27
|
@bot = bot
|
24
28
|
@channel_factory = channel_factory
|
@@ -46,7 +50,7 @@ module Kybus
|
|
46
50
|
end
|
47
51
|
|
48
52
|
def search_command_with_inline_arg(message)
|
49
|
-
command, values = @channel_factory.command_with_inline_arg(message.raw_message)
|
53
|
+
command, values = @channel_factory.command_with_inline_arg(message.raw_message || '')
|
50
54
|
if command
|
51
55
|
execution_context.command = command
|
52
56
|
values.each do |value|
|
@@ -59,6 +63,7 @@ module Kybus
|
|
59
63
|
end
|
60
64
|
|
61
65
|
def save_token!(message)
|
66
|
+
execution_context.set_last_message(message.serialize)
|
62
67
|
if execution_context.expecting_command?
|
63
68
|
command = @channel_factory.command(message.command)
|
64
69
|
if @inline_args && !command
|
@@ -86,8 +91,8 @@ module Kybus
|
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
89
|
-
def precommand_hook(&
|
90
|
-
@precommand_hook = proc(&
|
94
|
+
def precommand_hook(&)
|
95
|
+
@precommand_hook = proc(&)
|
91
96
|
end
|
92
97
|
|
93
98
|
def fallback(error)
|
@@ -106,12 +111,7 @@ module Kybus
|
|
106
111
|
retry
|
107
112
|
end
|
108
113
|
|
109
|
-
def invoke(
|
110
|
-
command = @channel_factory.command(command_name)
|
111
|
-
if command.nil? || command.params_size != args.size
|
112
|
-
raise "Wrong redirect #{command_name}, #{bot.registered_commands}"
|
113
|
-
end
|
114
|
-
|
114
|
+
def invoke(command, args)
|
115
115
|
state.command = command
|
116
116
|
command.params.zip(args).each do |param, value|
|
117
117
|
state.store_param(param, value)
|
@@ -119,11 +119,19 @@ module Kybus
|
|
119
119
|
run_command_or_prepare!
|
120
120
|
end
|
121
121
|
|
122
|
+
def redirect(command_name, args)
|
123
|
+
command = @channel_factory.command(command_name)
|
124
|
+
if command.nil? || command.params_size != args.size
|
125
|
+
raise "Wrong redirect #{command_name}, #{bot.registered_commands}"
|
126
|
+
end
|
127
|
+
|
128
|
+
invoke(command, args)
|
129
|
+
end
|
130
|
+
|
122
131
|
# Sends a message to get the next parameter from the user
|
123
132
|
def ask_param(param, label = nil)
|
124
|
-
provider = bot.provider
|
125
133
|
msg = label || "I need you to tell me #{param}"
|
126
|
-
bot.send_message(msg,
|
134
|
+
bot.send_message(msg, last_message.channel_id)
|
127
135
|
execution_context.next_param = param
|
128
136
|
end
|
129
137
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Kybus
|
4
4
|
module Bot
|
5
5
|
class DSLMethods
|
6
|
+
include Kybus::Logger
|
7
|
+
|
6
8
|
attr_accessor :state
|
7
9
|
attr_reader :provider
|
8
10
|
|
@@ -42,6 +44,10 @@ module Kybus
|
|
42
44
|
state.files
|
43
45
|
end
|
44
46
|
|
47
|
+
def metadata
|
48
|
+
state.metadata
|
49
|
+
end
|
50
|
+
|
45
51
|
def file(name)
|
46
52
|
(file = files[name]) && provider.file_builder(file)
|
47
53
|
end
|
@@ -51,28 +57,32 @@ module Kybus
|
|
51
57
|
end
|
52
58
|
|
53
59
|
def current_user
|
54
|
-
|
60
|
+
last_message.user
|
55
61
|
end
|
56
62
|
|
57
63
|
def is_private?
|
58
|
-
|
64
|
+
last_message.is_private?
|
59
65
|
end
|
60
66
|
|
61
67
|
def last_message
|
62
|
-
|
68
|
+
state.last_message
|
63
69
|
end
|
64
70
|
|
65
71
|
# returns the current_channel from where the message was sent
|
66
72
|
def current_channel
|
67
|
-
|
73
|
+
last_message.channel_id
|
68
74
|
end
|
69
75
|
|
70
76
|
def command_name
|
71
77
|
state&.command&.name
|
72
78
|
end
|
73
79
|
|
74
|
-
def
|
75
|
-
@bot.
|
80
|
+
def redirect(*args)
|
81
|
+
@bot.redirect(*args)
|
82
|
+
end
|
83
|
+
|
84
|
+
def abort(msg = nil)
|
85
|
+
raise ::Kybus::Bot::Base::AbortError, msg
|
76
86
|
end
|
77
87
|
end
|
78
88
|
end
|
data/lib/kybus/bot/message.rb
CHANGED
@@ -7,6 +7,7 @@ module Kybus
|
|
7
7
|
module Bot
|
8
8
|
# Base implementation for messages from distinct providers
|
9
9
|
class Message
|
10
|
+
require_relative 'serialized_message'
|
10
11
|
# Converts the messages into a hash
|
11
12
|
# Returns true when the received message is a command. Convention states
|
12
13
|
# that messages should start with '/' to be considered commands
|
@@ -15,7 +16,20 @@ module Kybus
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def command
|
18
|
-
raw_message&.split
|
19
|
+
raw_message&.split&.first
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize
|
23
|
+
SerializedMessage.new({
|
24
|
+
provider: self.class.name,
|
25
|
+
channel_id:,
|
26
|
+
message_id:,
|
27
|
+
user:,
|
28
|
+
replied_message: reply? ? replied_message.serialize : nil,
|
29
|
+
raw_message:,
|
30
|
+
is_private?: is_private?,
|
31
|
+
attachment: has_attachment? ? attachment : nil
|
32
|
+
})
|
19
33
|
end
|
20
34
|
end
|
21
35
|
end
|
data/lib/kybus/bot/migrator.rb
CHANGED
@@ -1,21 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'sequel'
|
4
|
-
require 'sequel/extensions/migration'
|
5
3
|
module Kybus
|
6
4
|
module Bot
|
7
5
|
module Migrator
|
8
6
|
class << self
|
9
|
-
def run_migrations!(
|
7
|
+
def run_migrations!(config)
|
8
|
+
case config['name']
|
9
|
+
when 'sequel'
|
10
|
+
run_sequel_migrations(config)
|
11
|
+
when 'dynamoid'
|
12
|
+
run_dynamoid_migrations(config)
|
13
|
+
else
|
14
|
+
raise "Provider not supported #{config['name']}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def run_sequel_migrations(config)
|
21
|
+
require 'sequel'
|
22
|
+
require 'sequel/extensions/migration'
|
23
|
+
|
24
|
+
conn = Sequel.connect(config['endpoint'])
|
10
25
|
conn.create_table?(:bot_sessions) do
|
11
26
|
String :channel_id
|
12
27
|
String :user
|
13
28
|
String :params, text: true
|
29
|
+
String :metadata, text: true
|
14
30
|
String :files, text: true
|
15
31
|
String :cmd
|
16
32
|
String :requested_param
|
33
|
+
String :last_message, text: true
|
17
34
|
end
|
18
35
|
end
|
36
|
+
|
37
|
+
def run_dynamoid_migrations(config)
|
38
|
+
repository = Kybus::Storage::Datasource::DynamoidRepository.from_config(
|
39
|
+
'name' => 'dynamoid',
|
40
|
+
'dynamoid_config' => true,
|
41
|
+
'access_key' => config['access_key'],
|
42
|
+
'secret_key' => config['secret_key'],
|
43
|
+
'region' => config['region'],
|
44
|
+
'endpoint' => config['endpoint'],
|
45
|
+
'namespace' => config['namespace'],
|
46
|
+
'table' => 'bot_sessions',
|
47
|
+
'primary_key' => 'channel_id',
|
48
|
+
'fields' => Base::DYNAMOID_FIELDS,
|
49
|
+
'read_capacity' => config['read_capacity'] || 1,
|
50
|
+
'write_capacity' => config['write_capacity'] || 1
|
51
|
+
)
|
52
|
+
|
53
|
+
# Ensure the table is created
|
54
|
+
return if Dynamoid.adapter.list_tables.include?('bot_sessions')
|
55
|
+
|
56
|
+
repository.model_class.create_table(sync: true)
|
57
|
+
end
|
19
58
|
end
|
20
59
|
end
|
21
60
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
# Base iplementation for messages from distinct providers
|
6
|
+
class SerializedMessage < Message
|
7
|
+
MANDATORY_FIELDS = %i[channel_id provider message_id user raw_message].freeze
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
data = data.to_h if data.is_a?(SerializedMessage)
|
11
|
+
raise 'BadSerializedMessage: nil message' if data.nil?
|
12
|
+
|
13
|
+
data = data[:data] if data.is_a?(Hash) && data.key?(:data)
|
14
|
+
|
15
|
+
missing_keys = MANDATORY_FIELDS.reject { |k| data.keys.include?(k) }
|
16
|
+
raise "BadSerializedMessage: Missing keys `#{missing_keys}', got: #{data}" unless missing_keys.empty?
|
17
|
+
|
18
|
+
@data = data.is_a?(String) ? JSON.parse(data, symbolize_names: true) : data
|
19
|
+
@data[:replied_message] = SerializedMessage.new(@data[:replied_message]) if @data[:replied_message]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.from_json(json)
|
23
|
+
data = json.is_a?(String) ? JSON.parse(json, symbolize_names: true) : json
|
24
|
+
new(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def reply?
|
28
|
+
@data[:replied_message].is_a?(SerializedMessage)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_attachment?
|
32
|
+
!@data[attachment].nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(method, *_args)
|
36
|
+
@data[method]
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_h
|
40
|
+
@data.dup
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_json(*args)
|
44
|
+
@data.to_json(*args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sidekiq'
|
4
|
+
require_relative 'sidekiq_dsl_methods'
|
5
|
+
|
6
|
+
module Kybus
|
7
|
+
module Bot
|
8
|
+
class SidekiqWorker
|
9
|
+
include Sidekiq::Worker
|
10
|
+
extend Kybus::DRY::ResourceInjector
|
11
|
+
include Kybus::Logger
|
12
|
+
|
13
|
+
def provider
|
14
|
+
SidekiqWorker.resource(:provider)
|
15
|
+
end
|
16
|
+
|
17
|
+
def factory
|
18
|
+
SidekiqWorker.resource(:factory)
|
19
|
+
end
|
20
|
+
|
21
|
+
def bot
|
22
|
+
SidekiqWorker.resource(:bot)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_context(details_json)
|
26
|
+
state = CommandState.from_json(details_json, factory)
|
27
|
+
log_debug('Loaded message into worker', state: state.to_h)
|
28
|
+
[SidekiqDSLMethods.new(provider, state, bot, factory), state]
|
29
|
+
end
|
30
|
+
|
31
|
+
def perform(details_json)
|
32
|
+
dsl, state = build_context(details_json)
|
33
|
+
dsl.instance_eval(&state.command.block)
|
34
|
+
rescue StandardError => e
|
35
|
+
log_error('Error in worker', error: e.class, msg: e.message, trace: e.backtrace)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class SidekiqCommandExecutor < CommandExecutor
|
40
|
+
def initialize(bot, channel_factory, configs)
|
41
|
+
super(bot, channel_factory, configs['inline_args'])
|
42
|
+
SidekiqWorker.register(:factory, @channel_factory)
|
43
|
+
SidekiqWorker.register(:provider, bot.provider)
|
44
|
+
SidekiqWorker.register(:bot, bot)
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_command!
|
48
|
+
log_debug(msg: 'Enqueued process to sidekiq', state: state.to_h)
|
49
|
+
SidekiqWorker.perform_async(state.to_json)
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
class SidekiqDSLMethods < DSLMethods
|
6
|
+
def initialize(provider, state, bot, factory)
|
7
|
+
super(provider, state, bot)
|
8
|
+
@factory = factory
|
9
|
+
end
|
10
|
+
|
11
|
+
def redirect(command_name, args = {})
|
12
|
+
command = @factory.command(command_name)
|
13
|
+
raise "Wrong redirect #{command_name}" if command.nil? || command.params_size != args.size
|
14
|
+
|
15
|
+
state.command = command
|
16
|
+
command.params.zip(args).each do |param, value|
|
17
|
+
state.store_param(param, value)
|
18
|
+
end
|
19
|
+
|
20
|
+
instance_eval(&state.command.block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/kybus/bot/test.rb
CHANGED
@@ -31,7 +31,7 @@ module Kybus
|
|
31
31
|
def receives(msg, attachments = nil)
|
32
32
|
attachments = Adapter::DebugMessage::DebugFile.new(attachments) if attachments
|
33
33
|
msg = Adapter::DebugMessage.new(msg, @default_channel_id, attachments)
|
34
|
-
log_info('Received message', channel: @default_channel_id, msg:)
|
34
|
+
log_info('Received message', channel: @default_channel_id, msg: msg.raw_message)
|
35
35
|
provider.last_message = msg
|
36
36
|
executor.process_message(msg)
|
37
37
|
end
|
data/lib/kybus/bot/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kybus-bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gilberto Vargas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kybus-core
|
@@ -24,188 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.1'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: kybus-logger
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.1'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.1'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: kybus-storage
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0.1'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0.1'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: minitest
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '5.11'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '5.11'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: mocha
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.8'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.8'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: pry
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0.12'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0.12'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rack-minitest
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0.0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0.0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rake
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '12.3'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '12.3'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: rdoc
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - "~>"
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '6.1'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - "~>"
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '6.1'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: sequel
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: simplecov
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - "~>"
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0.16'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - "~>"
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0.16'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: sqlite3
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: telegram-bot-ruby
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - ">="
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0'
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - ">="
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: '0'
|
195
|
-
- !ruby/object:Gem::Dependency
|
196
|
-
name: webmock
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
198
|
-
requirements:
|
199
|
-
- - "~>"
|
200
|
-
- !ruby/object:Gem::Version
|
201
|
-
version: '3.5'
|
202
|
-
type: :development
|
203
|
-
prerelease: false
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
205
|
-
requirements:
|
206
|
-
- - "~>"
|
207
|
-
- !ruby/object:Gem::Version
|
208
|
-
version: '3.5'
|
209
27
|
description: Provides a framework for building bots with ruby
|
210
28
|
email:
|
211
29
|
- tachoguitar@gmail.com
|
@@ -230,6 +48,9 @@ files:
|
|
230
48
|
- lib/kybus/bot/dsl_methods.rb
|
231
49
|
- lib/kybus/bot/message.rb
|
232
50
|
- lib/kybus/bot/migrator.rb
|
51
|
+
- lib/kybus/bot/serialized_message.rb
|
52
|
+
- lib/kybus/bot/sidekiq_command_executor.rb
|
53
|
+
- lib/kybus/bot/sidekiq_dsl_methods.rb
|
233
54
|
- lib/kybus/bot/test.rb
|
234
55
|
- lib/kybus/bot/testing.rb
|
235
56
|
- lib/kybus/bot/version.rb
|
@@ -253,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
253
74
|
- !ruby/object:Gem::Version
|
254
75
|
version: '0'
|
255
76
|
requirements: []
|
256
|
-
rubygems_version: 3.
|
77
|
+
rubygems_version: 3.5.14
|
257
78
|
signing_key:
|
258
79
|
specification_version: 4
|
259
80
|
summary: Provides a framework for building bots with ruby
|