kybus-bot 0.4.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 +7 -0
- data/lib/kybus/bot.rb +8 -0
- data/lib/kybus/bot/adapters/base.rb +16 -0
- data/lib/kybus/bot/adapters/debug.rb +143 -0
- data/lib/kybus/bot/adapters/discord.rb +115 -0
- data/lib/kybus/bot/adapters/telegram.rb +107 -0
- data/lib/kybus/bot/base.rb +220 -0
- data/lib/kybus/bot/command_definition.rb +45 -0
- data/lib/kybus/bot/message.rb +25 -0
- data/lib/kybus/bot/migrator.rb +21 -0
- data/lib/kybus/bot/testing.rb +27 -0
- data/lib/kybus/bot/version.rb +7 -0
- metadata +250 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 189f024f3c6411b1c2c7d918a7ccfca6afd2cffe096c53f76522932c74be1b17
|
4
|
+
data.tar.gz: 9f9b3928b68ea6b1fc1e6ae204d5e0a34223c3e909b2986bae0c477b848d73ca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 234bf15cd1c11549de4b8d3513a7abc50f76ffaae56aeb2248a112007b540fccf0e6a42f195c67b7a2a5032d13930e20f2384fb117ab86194d564c30ebb1d423
|
7
|
+
data.tar.gz: f3c6e1743cea030340a677d6b7674932ded040e2ac4febfcc24eacfb76e7f36d39bbec85a908c52fe7e06bf782364a11abebc4f941b1d314b60e48fed7ed7766
|
data/lib/kybus/bot.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
# Implements a factory singleton for building bot adapters
|
6
|
+
module Adapter
|
7
|
+
extend Kybus::DRY::ResourceInjector
|
8
|
+
|
9
|
+
# builds the abstract adapter
|
10
|
+
def self.from_config(configs)
|
11
|
+
require_relative configs['name']
|
12
|
+
resource(configs['name']).new(configs)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
# :nodoc: #
|
6
|
+
module Adapter
|
7
|
+
# :nodoc: #
|
8
|
+
# Wraps a debugging message inside a class.
|
9
|
+
class DebugMessage < Kybus::Bot::Message
|
10
|
+
# It receives a string with the raw text and the id of the channel
|
11
|
+
def initialize(text, channel)
|
12
|
+
@text = text
|
13
|
+
@channel = channel
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the channel id
|
17
|
+
def channel_id
|
18
|
+
"debug_message__#{@channel}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the message contents
|
22
|
+
def raw_message
|
23
|
+
@text
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# This class simulates a message chat with a user.
|
28
|
+
class Channel
|
29
|
+
# It is build from
|
30
|
+
# an array of raw messages, the name of the channel and the config
|
31
|
+
# to enable debug messages
|
32
|
+
def initialize(messages, name, echo)
|
33
|
+
@state = :open
|
34
|
+
@pending_messages = messages.dup
|
35
|
+
@name = name
|
36
|
+
@echo = echo
|
37
|
+
end
|
38
|
+
|
39
|
+
# Checks if there are messages open or that has not been answered
|
40
|
+
def open?
|
41
|
+
@state == :open
|
42
|
+
end
|
43
|
+
|
44
|
+
# Checks if there are still messages in the channel
|
45
|
+
def empty?
|
46
|
+
@pending_messages.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns the next message in the buffer
|
50
|
+
def read_message
|
51
|
+
@state = :closed
|
52
|
+
DebugMessage.new(@pending_messages.shift, @name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_data(message)
|
56
|
+
return unless @echo
|
57
|
+
|
58
|
+
puts "Sending message to channel: #{@name}"
|
59
|
+
puts message
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_writer :echo
|
63
|
+
|
64
|
+
# receives the answer from the bot
|
65
|
+
def answer(message)
|
66
|
+
send_data(message)
|
67
|
+
@state = :open
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# This adapter is intended to be used on unit tests and development.
|
73
|
+
class Debug
|
74
|
+
# Exception for stoping the loop of messages
|
75
|
+
class NoMoreMessageException < Kybus::Exceptions::AntError
|
76
|
+
def initialize
|
77
|
+
super('There are no messages left')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# It receives a hash with the configurations:
|
82
|
+
# - name: the name of the channel
|
83
|
+
# - channels a key value, where the key is a name and the value the
|
84
|
+
# list of the messages in the channel.
|
85
|
+
# - echo: a flag to enable debug messages.
|
86
|
+
def initialize(configs)
|
87
|
+
@channels = {}
|
88
|
+
configs['channels'].each do |name, messages|
|
89
|
+
@channels[name] = Channel.new(messages, name, configs['echo'])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Interface for receiving message
|
94
|
+
def read_message
|
95
|
+
# take the first message from the first open message,
|
96
|
+
# then rotate the array
|
97
|
+
loop do
|
98
|
+
raise NoMoreMessageException if @channels.values.all?(&:empty?)
|
99
|
+
|
100
|
+
msg = @channels.values.find(&:open?)
|
101
|
+
return msg.read_message if msg
|
102
|
+
|
103
|
+
# :nocov: #
|
104
|
+
sleep(1)
|
105
|
+
# :nocov: #
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# removes prefix from channel id
|
110
|
+
def channel(name)
|
111
|
+
@channels[name.gsub('debug_message__', '')]
|
112
|
+
end
|
113
|
+
|
114
|
+
# interface for sending messages
|
115
|
+
def send_message(channel_name, contents)
|
116
|
+
channel(channel_name).answer(contents)
|
117
|
+
end
|
118
|
+
|
119
|
+
# interface for sending video
|
120
|
+
def send_video(channel_name, video_url)
|
121
|
+
channel(channel_name).answer("VIDEO: #{video_url}")
|
122
|
+
end
|
123
|
+
|
124
|
+
# interface for sending uadio
|
125
|
+
def send_audio(channel_name, audio_url)
|
126
|
+
channel(channel_name).answer("AUDIO: #{audio_url}")
|
127
|
+
end
|
128
|
+
|
129
|
+
# interface for sending image
|
130
|
+
def send_image(channel_name, image_url)
|
131
|
+
channel(channel_name).answer("IMG: #{image_url}")
|
132
|
+
end
|
133
|
+
|
134
|
+
# changes echo config
|
135
|
+
def echo=(toogle)
|
136
|
+
@channels.each { |_, channel| channel.echo = toogle }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
register('debug', Debug)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'discordrb'
|
4
|
+
|
5
|
+
module Kybus
|
6
|
+
module Bot
|
7
|
+
# :nodoc: #
|
8
|
+
module Adapter
|
9
|
+
# :nodoc: #
|
10
|
+
# Wraps a debugging message inside a class.
|
11
|
+
class DiscordMessage < Kybus::Bot::Message
|
12
|
+
# It receives a string with the raw text and the id of the channel
|
13
|
+
def initialize(msg)
|
14
|
+
@message = msg
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the channel id
|
18
|
+
def channel_id
|
19
|
+
@message.channel.id
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the message contents
|
23
|
+
def raw_message
|
24
|
+
@message.content
|
25
|
+
end
|
26
|
+
|
27
|
+
def user
|
28
|
+
@message.author.id
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_private?
|
32
|
+
@message.channel.private?
|
33
|
+
end
|
34
|
+
|
35
|
+
def reply?
|
36
|
+
@message.message.reply?
|
37
|
+
end
|
38
|
+
|
39
|
+
def replied_message
|
40
|
+
DiscordMessage.new(@message.message.referenced_message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# This adapter is intended to be used on unit tests and development.
|
46
|
+
class Discord
|
47
|
+
include ::Kybus::Logger
|
48
|
+
# It receives a hash with the configurations:
|
49
|
+
# - name: the name of the channel
|
50
|
+
# - channels a key value, where the key is a name and the value the
|
51
|
+
# list of the messages in the channel.
|
52
|
+
# - echo: a flag to enable debug messages.
|
53
|
+
def initialize(configs)
|
54
|
+
@config = configs
|
55
|
+
@client = Discordrb::Bot.new(token: @config['token'])
|
56
|
+
@pool = []
|
57
|
+
@client.message do |msg|
|
58
|
+
@pool << msg
|
59
|
+
end
|
60
|
+
@client.run(:async)
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :client
|
64
|
+
|
65
|
+
def mention(id)
|
66
|
+
"<@!#{id}>"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Interface for receiving message
|
70
|
+
def read_message
|
71
|
+
# take the first message from the first open message,
|
72
|
+
loop do
|
73
|
+
if @pool.empty?
|
74
|
+
sleep(0.1)
|
75
|
+
else
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
DiscordMessage.new(@pool.shift)
|
80
|
+
end
|
81
|
+
|
82
|
+
# interface for sending messages
|
83
|
+
def send_message(channel_name, contents)
|
84
|
+
puts "#{channel_name} => #{contents}" if @config['debug']
|
85
|
+
channel = @client.channel(channel_name)
|
86
|
+
if channel
|
87
|
+
channel.send_message(contents)
|
88
|
+
else
|
89
|
+
@client.user(channel_name).pm(contents)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# # interface for sending video
|
94
|
+
# def send_video(channel_name, video_url)
|
95
|
+
# file = Faraday::UploadIO.new(video_url, 'video/mp4')
|
96
|
+
# @client.api.send_video(chat_id: channel_name, audio: file)
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# # interface for sending uadio
|
100
|
+
# def send_audio(channel_name, audio_url)
|
101
|
+
# file = Faraday::UploadIO.new(audio_url, 'audio/mp3')
|
102
|
+
# @client.api.send_audio(chat_id: channel_name, audio: file)
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# # interface for sending image
|
106
|
+
# def send_image(channel_name, image_url)
|
107
|
+
# file = Faraday::UploadIO.new(image_url, 'image/jpeg')
|
108
|
+
# @client.api.send_photo(chat_id: channel_name, photo: file)
|
109
|
+
# end
|
110
|
+
end
|
111
|
+
|
112
|
+
register('discord', Discord)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'telegram/bot'
|
4
|
+
|
5
|
+
module Kybus
|
6
|
+
module Bot
|
7
|
+
# :nodoc: #
|
8
|
+
module Adapter
|
9
|
+
# :nodoc: #
|
10
|
+
# Wraps a debugging message inside a class.
|
11
|
+
class TelegramMessage < Kybus::Bot::Message
|
12
|
+
# It receives a string with the raw text and the id of the channel
|
13
|
+
def initialize(message)
|
14
|
+
@message = message
|
15
|
+
end
|
16
|
+
|
17
|
+
def reply?
|
18
|
+
!!@message.reply_to_message
|
19
|
+
end
|
20
|
+
|
21
|
+
def replied_message
|
22
|
+
TelegramMessage.new(@message.reply_to_message)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the channel id
|
26
|
+
def channel_id
|
27
|
+
@message.chat.id
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the message contents
|
31
|
+
def raw_message
|
32
|
+
@message.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_private?
|
36
|
+
@message.chat.type == 'private'
|
37
|
+
end
|
38
|
+
|
39
|
+
def user
|
40
|
+
@message.from.id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# This adapter is intended to be used on unit tests and development.
|
46
|
+
class Telegram
|
47
|
+
include ::Kybus::Logger
|
48
|
+
# It receives a hash with the configurations:
|
49
|
+
# - name: the name of the channel
|
50
|
+
# - channels a key value, where the key is a name and the value the
|
51
|
+
# list of the messages in the channel.
|
52
|
+
# - echo: a flag to enable debug messages.
|
53
|
+
def initialize(configs)
|
54
|
+
@config = configs
|
55
|
+
@client = ::Telegram::Bot::Client.new(@config['token'])
|
56
|
+
end
|
57
|
+
|
58
|
+
# Interface for receiving message
|
59
|
+
def read_message
|
60
|
+
# take the first message from the first open message,
|
61
|
+
loop do
|
62
|
+
@client.listen do |message|
|
63
|
+
log_info('Received message', message: message.to_h,
|
64
|
+
from: message.from.to_h)
|
65
|
+
return TelegramMessage.new(message)
|
66
|
+
end
|
67
|
+
rescue Telegram::Bot::Exceptions::ResponseError => e
|
68
|
+
log_error('An error ocurred while calling to Telegram API', e)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def mention(id)
|
73
|
+
"[user](tg://user?id=#{id})"
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# interface for sending messages
|
78
|
+
def send_message(channel_name, contents)
|
79
|
+
puts "#{channel_name} => #{contents}" if @config['debug']
|
80
|
+
@client.api.send_message(chat_id: channel_name, text: contents)
|
81
|
+
rescue Telegram::Bot::Exceptions::ResponseError => err
|
82
|
+
return if err[:error_code] == '403'
|
83
|
+
end
|
84
|
+
|
85
|
+
# interface for sending video
|
86
|
+
def send_video(channel_name, video_url)
|
87
|
+
file = Faraday::UploadIO.new(video_url, 'video/mp4')
|
88
|
+
@client.api.send_video(chat_id: channel_name, audio: file)
|
89
|
+
end
|
90
|
+
|
91
|
+
# interface for sending uadio
|
92
|
+
def send_audio(channel_name, audio_url)
|
93
|
+
file = Faraday::UploadIO.new(audio_url, 'audio/mp3')
|
94
|
+
@client.api.send_audio(chat_id: channel_name, audio: file)
|
95
|
+
end
|
96
|
+
|
97
|
+
# interface for sending image
|
98
|
+
def send_image(channel_name, image_url)
|
99
|
+
file = Faraday::UploadIO.new(image_url, 'image/jpeg')
|
100
|
+
@client.api.send_photo(chat_id: channel_name, photo: file)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
register('telegram', Telegram)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kybus/dry/daemon'
|
4
|
+
require 'kybus/bot/adapters/base'
|
5
|
+
require 'kybus/storage'
|
6
|
+
require_relative 'command_definition'
|
7
|
+
|
8
|
+
require 'kybus/logger'
|
9
|
+
|
10
|
+
module Kybus
|
11
|
+
module Bot
|
12
|
+
# Base class for bot implementation. It wraps the threads execution, the
|
13
|
+
# provider and the state storage inside an object.
|
14
|
+
class Base
|
15
|
+
include Kybus::Storage::Datasource
|
16
|
+
include Kybus::Logger
|
17
|
+
attr_reader :provider
|
18
|
+
|
19
|
+
def send_message(content, channel = nil)
|
20
|
+
@provider.send_message(channel || current_channel, content)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rescue_from(klass, &block)
|
24
|
+
@commands.register_command(klass, [], block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_image(content, channel = nil)
|
28
|
+
@provider.send_image(channel || current_channel, content)
|
29
|
+
end
|
30
|
+
|
31
|
+
def send_audio(content, channel = nil)
|
32
|
+
@provider.send_audio(channel || current_channel, content)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Configurations needed:
|
36
|
+
# - pool_size: number of threads created in execution
|
37
|
+
# - provider: a configuration for a thread provider.
|
38
|
+
# See supported adapters
|
39
|
+
# - name: The bot name
|
40
|
+
# - repository: Configurations about the state storage
|
41
|
+
def initialize(configs)
|
42
|
+
@pool_size = configs['pool_size']
|
43
|
+
@provider = Kybus::Bot::Adapter.from_config(configs['provider'])
|
44
|
+
@commands = Kybus::Bot::CommandDefinition.new
|
45
|
+
|
46
|
+
# TODO: move this to config
|
47
|
+
@repository = Kybus::Storage::Repository.from_config(
|
48
|
+
nil,
|
49
|
+
configs['state_repository']
|
50
|
+
.merge('primary_key' => 'channel_id',
|
51
|
+
'table' => 'bot_sessions'),
|
52
|
+
{}
|
53
|
+
)
|
54
|
+
@factory = Kybus::Storage::Factory.new(EmptyModel)
|
55
|
+
@factory.register(:default, :json)
|
56
|
+
@factory.register(:json, @repository)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Starts the bot execution, this is a blocking call.
|
60
|
+
def run
|
61
|
+
@pool = Array.new(@pool_size) do
|
62
|
+
# TODO: Create a subclass with the context execution
|
63
|
+
Kybus::DRY::Daemon.new(@pool_size, true) do
|
64
|
+
message = @provider.read_message
|
65
|
+
@last_message = message
|
66
|
+
process_message(message)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# TODO: Implement an interface for killing the process
|
70
|
+
@pool.each(&:run)
|
71
|
+
# :nocov: #
|
72
|
+
@pool.each(&:await)
|
73
|
+
# :nocov: #
|
74
|
+
end
|
75
|
+
|
76
|
+
# Process a single message, this method can be overwriten to enable
|
77
|
+
# more complex implementations of commands. It receives a message object.
|
78
|
+
def process_message(message)
|
79
|
+
run_simple_command!(message)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Executes a command with the easiest definition. It runs a state machine:
|
83
|
+
# - If the message is a command, set the status to asking params
|
84
|
+
# - If the message is a param, stores it
|
85
|
+
# - If the command is ready to be executed, trigger it.
|
86
|
+
def run_simple_command!(message)
|
87
|
+
load_state!(message.channel_id)
|
88
|
+
log_debug('loaded state', message: message.to_h, state: @state.to_h)
|
89
|
+
if message.command?
|
90
|
+
self.command = message.raw_message
|
91
|
+
else
|
92
|
+
add_param(message.raw_message)
|
93
|
+
end
|
94
|
+
if command_ready?
|
95
|
+
run_command!
|
96
|
+
else
|
97
|
+
ask_param(next_missing_param)
|
98
|
+
end
|
99
|
+
save_state!
|
100
|
+
rescue StandardError => e
|
101
|
+
catch = @commands[e.class]
|
102
|
+
raise if catch.nil?
|
103
|
+
|
104
|
+
instance_eval(&catch.block)
|
105
|
+
clear_command
|
106
|
+
end
|
107
|
+
|
108
|
+
# DSL method for adding simple commands
|
109
|
+
def register_command(name, params = [], &block)
|
110
|
+
@commands.register_command(name, params, block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Method for triggering command
|
114
|
+
def run_command!
|
115
|
+
instance_eval(¤t_command_object.block)
|
116
|
+
clear_command
|
117
|
+
end
|
118
|
+
|
119
|
+
def clear_command
|
120
|
+
@state[:cmd] = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
# Checks if the command is ready to be executed
|
124
|
+
def command_ready?
|
125
|
+
cmd = current_command_object
|
126
|
+
cmd.ready?(current_params)
|
127
|
+
end
|
128
|
+
|
129
|
+
# loads parameters from state
|
130
|
+
def current_params
|
131
|
+
@state[:params] || {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def params
|
135
|
+
current_params
|
136
|
+
end
|
137
|
+
|
138
|
+
def mention(name)
|
139
|
+
@provider.mention(name)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Loads command from state
|
143
|
+
def current_command_object
|
144
|
+
command = @state[:cmd]
|
145
|
+
@commands[command] || @commands['default']
|
146
|
+
end
|
147
|
+
|
148
|
+
# returns the current_channel from where the message was sent
|
149
|
+
def current_channel
|
150
|
+
@state[:channel_id]
|
151
|
+
end
|
152
|
+
|
153
|
+
def current_user
|
154
|
+
@last_message.user
|
155
|
+
end
|
156
|
+
|
157
|
+
def is_private?
|
158
|
+
@last_message.is_private?
|
159
|
+
end
|
160
|
+
|
161
|
+
# stores the command into state
|
162
|
+
def command=(cmd)
|
163
|
+
log_debug('Message set as command', command: cmd)
|
164
|
+
|
165
|
+
@state[:cmd] = cmd.split(' ').first
|
166
|
+
@state[:params] = {}
|
167
|
+
end
|
168
|
+
|
169
|
+
# validates which is the following parameter required
|
170
|
+
def next_missing_param
|
171
|
+
current_command_object.next_missing_param(current_params)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Sends a message to get the next parameter from the user
|
175
|
+
def ask_param(param)
|
176
|
+
log_debug('I\'m going to ask the next param', param: param)
|
177
|
+
@provider.send_message(current_channel,
|
178
|
+
"I need you to tell me #{param}")
|
179
|
+
@state[:requested_param] = param.to_s
|
180
|
+
end
|
181
|
+
|
182
|
+
# Stores a parameter into the status
|
183
|
+
def add_param(value)
|
184
|
+
return if @state[:requested_param].nil?
|
185
|
+
|
186
|
+
log_debug('Received new param',
|
187
|
+
param: @state[:requested_param].to_sym,
|
188
|
+
value: value)
|
189
|
+
|
190
|
+
@state[:params][@state[:requested_param].to_sym] = value
|
191
|
+
end
|
192
|
+
|
193
|
+
# Loads the state from storage
|
194
|
+
def load_state!(channel)
|
195
|
+
@state = load_state(channel)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Private implementation for load message
|
199
|
+
def load_state(channel)
|
200
|
+
data = @factory.get(channel)
|
201
|
+
data[:params] = JSON.parse(data[:params], symbolize_names: true)
|
202
|
+
data
|
203
|
+
rescue Kybus::Storage::Exceptions::ObjectNotFound
|
204
|
+
@factory.create(channel_id: channel, params: {}.to_json)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Saves the state into storage
|
208
|
+
def save_state!
|
209
|
+
json = @state[:params]
|
210
|
+
@state[:params] = json.to_json
|
211
|
+
@state.store
|
212
|
+
@state[:params] = json
|
213
|
+
end
|
214
|
+
|
215
|
+
def session
|
216
|
+
@repository
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kybus
|
4
|
+
module Bot
|
5
|
+
# Object that wraps a command, it is analogus to a route definition.
|
6
|
+
# it currently only gets a param list, but it will be extended to a more
|
7
|
+
# complex DSL.
|
8
|
+
class Command
|
9
|
+
attr_reader :block
|
10
|
+
|
11
|
+
# Receives a list of params as symbols and the lambda with the block.
|
12
|
+
def initialize(params, block)
|
13
|
+
@params = params
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
# Checks if the params object given contains all the needed values
|
18
|
+
def ready?(current_params)
|
19
|
+
@params.all? { |key| current_params.key?(key) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Finds the first empty param from the given parameter
|
23
|
+
def next_missing_param(current_params)
|
24
|
+
@params.find { |key| !current_params.key?(key) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Wraps a collection of commands.
|
29
|
+
class CommandDefinition
|
30
|
+
def initialize
|
31
|
+
@commands = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Stores an operation definition
|
35
|
+
def register_command(name, params, block)
|
36
|
+
@commands[name] = Command.new(params, block)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a command with the name
|
40
|
+
def [](name)
|
41
|
+
@commands[name]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kybus/dry/daemon'
|
4
|
+
require 'kybus/bot/adapters/base'
|
5
|
+
|
6
|
+
module Kybus
|
7
|
+
module Bot
|
8
|
+
# Base implementation for messages from distinct providers
|
9
|
+
class Message
|
10
|
+
# Converts the messages into a hash
|
11
|
+
def to_h
|
12
|
+
{
|
13
|
+
text: raw_message,
|
14
|
+
channel: channel_id
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true when the received message is a command. Convention states
|
19
|
+
# that messages should start with '/' to be considered commands
|
20
|
+
def command?
|
21
|
+
raw_message&.split(' ')&.first&.start_with?('/')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sequel'
|
4
|
+
require 'sequel/extensions/migration'
|
5
|
+
module Kybus
|
6
|
+
module Bot
|
7
|
+
module Migrator
|
8
|
+
class << self
|
9
|
+
def run_migrations!(conn)
|
10
|
+
conn.create_table?(:bot_sessions) do
|
11
|
+
String :channel_id
|
12
|
+
String :user
|
13
|
+
String :params, text: true
|
14
|
+
String :cmd
|
15
|
+
String :requested_param
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'adapters/debug'
|
4
|
+
require_relative 'base'
|
5
|
+
module Kybus
|
6
|
+
module Bot
|
7
|
+
class NonDebugAdapterInTesting < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Base class for bot implementation. It wraps the threads execution, the
|
11
|
+
# provider and the state storage inside an object.
|
12
|
+
class Base
|
13
|
+
include Kybus::Bot::Adapter
|
14
|
+
def stub_channels(messages)
|
15
|
+
raise(NonDebugAdapterInTesting) unless @provider.is_a?(Debug)
|
16
|
+
|
17
|
+
@provider = Debug.new('channels' => messages)
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_test
|
21
|
+
run
|
22
|
+
rescue Debug::NoMoreMessageException
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kybus-bot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gilberto Vargas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: kybus-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
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
|
+
description: Provides a framework for building bots with ruby
|
210
|
+
email:
|
211
|
+
- tachoguitar@gmail.com
|
212
|
+
executables: []
|
213
|
+
extensions: []
|
214
|
+
extra_rdoc_files: []
|
215
|
+
files:
|
216
|
+
- lib/kybus/bot.rb
|
217
|
+
- lib/kybus/bot/adapters/base.rb
|
218
|
+
- lib/kybus/bot/adapters/debug.rb
|
219
|
+
- lib/kybus/bot/adapters/discord.rb
|
220
|
+
- lib/kybus/bot/adapters/telegram.rb
|
221
|
+
- lib/kybus/bot/base.rb
|
222
|
+
- lib/kybus/bot/command_definition.rb
|
223
|
+
- lib/kybus/bot/message.rb
|
224
|
+
- lib/kybus/bot/migrator.rb
|
225
|
+
- lib/kybus/bot/testing.rb
|
226
|
+
- lib/kybus/bot/version.rb
|
227
|
+
homepage: https://github.com/tachomex/kybus
|
228
|
+
licenses:
|
229
|
+
- MIT
|
230
|
+
metadata: {}
|
231
|
+
post_install_message:
|
232
|
+
rdoc_options: []
|
233
|
+
require_paths:
|
234
|
+
- lib
|
235
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
236
|
+
requirements:
|
237
|
+
- - ">="
|
238
|
+
- !ruby/object:Gem::Version
|
239
|
+
version: '0'
|
240
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
241
|
+
requirements:
|
242
|
+
- - ">="
|
243
|
+
- !ruby/object:Gem::Version
|
244
|
+
version: '0'
|
245
|
+
requirements: []
|
246
|
+
rubygems_version: 3.1.4
|
247
|
+
signing_key:
|
248
|
+
specification_version: 4
|
249
|
+
summary: Provides a framework for building bots with ruby
|
250
|
+
test_files: []
|