ant-bot 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a4e8ae082331aa86f94717547ed3ac9a4fd97182269363d3e280260d9cac9971
4
+ data.tar.gz: 2e19ddde3d123d08770e89c6b3a33ea8f745c365c812df04400e339a95763543
5
+ SHA512:
6
+ metadata.gz: d80353dd1bb1a77abfefbdbe7855981f119fd32f1de1c967b7644f6f4d98a0953b7c44e6ac4357c44a05c53e89f99237bcd68ad4e8bc59d959cd620048ec8c26
7
+ data.tar.gz: a3056348c47d1b6d7e4057dbc4dffafff1cd792201ac42d73efe038187fb49ffb1bd193af9bd8954f587f84c4744d1c33a4cdf58fe63d5e9919da8f8c9b89312
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ require 'ant/core'
3
+ require 'ant/storage'
4
+
5
+ require_relative 'bot/base'
6
+ require_relative 'bot/message'
7
+ require_relative 'bot/version'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ant
4
+ module Bot
5
+ # Implements a factory singleton for building bot adapters
6
+ module Adapter
7
+ extend Ant::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,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ant
4
+ module Bot
5
+ # :nodoc: #
6
+ module Adapter
7
+ # :nodoc: #
8
+ # Wraps a debugging message inside a class.
9
+ class DebugMessage < Ant::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
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
+ def echo=(toogle)
63
+ @echo = toogle
64
+ end
65
+
66
+ # receives the answer from the bot
67
+ def answer(message)
68
+ send_data(message)
69
+ @state = :open
70
+ end
71
+ end
72
+
73
+ ##
74
+ # This adapter is intended to be used on unit tests and development.
75
+ class Debug
76
+ # Exception for stoping the loop of messages
77
+ class NoMoreMessageException < Ant::Exceptions::AntError
78
+ def initialize
79
+ super('There are no messages left')
80
+ end
81
+ end
82
+
83
+ # It receives a hash with the configurations:
84
+ # - name: the name of the channel
85
+ # - channels a key value, where the key is a name and the value the
86
+ # list of the messages in the channel.
87
+ # - echo: a flag to enable debug messages.
88
+ def initialize(configs)
89
+ @channels = {}
90
+ configs['channels'].each do |name, messages|
91
+ @channels[name] = Channel.new(messages, name, configs['echo'])
92
+ end
93
+ end
94
+
95
+ # Interface for receiving message
96
+ def read_message
97
+ # take the first message from the first open message,
98
+ # then rotate the array
99
+ loop do
100
+ raise NoMoreMessageException if @channels.values.all?(&:empty?)
101
+
102
+ msg = @channels.values.find(&:open?)
103
+ return msg.read_message if msg
104
+
105
+ # :nocov: #
106
+ sleep(1)
107
+ # :nocov: #
108
+ end
109
+ end
110
+
111
+ # removes prefix from channel id
112
+ def channel(name)
113
+ @channels[name.gsub('debug_message__', '')]
114
+ end
115
+
116
+ # interface for sending messages
117
+ def send_message(channel_name, contents)
118
+ channel(channel_name).answer(contents)
119
+ end
120
+
121
+ # interface for sending video
122
+ def send_video(channel_name, video_url)
123
+ channel(channel_name).answer("VIDEO: #{video_url}")
124
+ end
125
+
126
+ # interface for sending uadio
127
+ def send_audio(channel_name, audio_url)
128
+ channel(channel_name).answer("AUDIO: #{audio_url}")
129
+ end
130
+
131
+ # interface for sending image
132
+ def send_image(channel_name, image_url)
133
+ channel(channel_name).answer("IMG: #{image_url}")
134
+ end
135
+
136
+ # changes echo config
137
+ def echo=(toogle)
138
+ @channels.each { |_, channel| channel.echo = toogle }
139
+ end
140
+ end
141
+
142
+ register('debug', Debug)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ant
4
+ module Bot
5
+ module Adapter
6
+ # Interface for connecting with telegram api
7
+ # TODO: Implement this
8
+ class Telegram
9
+ def initialize(configs); end
10
+
11
+ # Interface for receiving message
12
+ def read_message
13
+ raise
14
+ end
15
+
16
+ # interface for sending messages
17
+ def send_message(_channel, _contents)
18
+ raise
19
+ end
20
+
21
+ # interface for sending video
22
+ def send_video(_channel, _video_url)
23
+ raise
24
+ end
25
+
26
+ # interface for sending uadio
27
+ def send_audio(_channel, _audio_url)
28
+ raise
29
+ end
30
+
31
+ # interface for sending image
32
+ def send_image(_channel, _image_url)
33
+ raise
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ant/dry/daemon'
4
+ require 'ant/bot/adapters/base'
5
+ require 'ant/storage'
6
+ require_relative 'command_definition'
7
+
8
+ require 'ant/logger'
9
+
10
+ module Ant
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 Ant::Storage::Datasource
16
+ include Ant::Logger
17
+ # Configurations needed:
18
+ # - pool_size: number of threads created in execution
19
+ # - provider: a configuration for a thread provider.
20
+ # See supported adapters
21
+ # - name: The bot name
22
+ # - repository: Configurations about the state storage
23
+ def initialize(configs)
24
+ @pool_size = configs['pool_size']
25
+ @provider = Ant::Bot::Adapter.from_config(configs['provider'])
26
+ @commands = Ant::Bot::CommandDefinition.new
27
+
28
+ # TODO: move this to config
29
+ @repository = Ant::Storage::Repository.from_config(
30
+ nil,
31
+ { 'name' => 'json',
32
+ 'schema_name' => configs['name'],
33
+ 'storage' => 'storage/$name',
34
+ 'primary_key' => 'channel_id' },
35
+ {}
36
+ )
37
+ @factory = Ant::Storage::Factory.new(EmptyModel)
38
+ @factory.register(:default, :json)
39
+ @factory.register(:json, @repository)
40
+ end
41
+
42
+ # Starts the bot execution, this is a blocking call.
43
+ def run
44
+ @pool = Array.new(@pool_size) do
45
+ # TODO: Create a subclass with the context execution
46
+ Ant::DRY::Daemon.new(@pool_size, true) do
47
+ message = @provider.read_message
48
+ process_message(message)
49
+ end
50
+ end
51
+ # TODO: Implement an interface for killing the process
52
+ @pool.each(&:run)
53
+ # :nocov: #
54
+ @pool.each(&:await)
55
+ # :nocov: #
56
+ end
57
+
58
+ # Process a single message, this method can be overwriten to enable
59
+ # more complex implementations of commands. It receives a message object.
60
+ def process_message(message)
61
+ run_simple_command!(message)
62
+ end
63
+
64
+ # Executes a command with the easiest definition. It runs a state machine:
65
+ # - If the message is a command, set the status to asking params
66
+ # - If the message is a param, stores it
67
+ # - If the command is ready to be executed, trigger it.
68
+ def run_simple_command!(message)
69
+ load_state!(message.channel_id)
70
+ log_debug('loaded state', message: message.to_h, state: @state.to_h)
71
+ if message.command?
72
+ self.command = message
73
+ else
74
+ add_param(message)
75
+ end
76
+ if command_ready?
77
+ run_command!
78
+ else
79
+ ask_param(next_missing_param)
80
+ end
81
+ save_state!
82
+ end
83
+
84
+ # DSL method for adding simple commands
85
+ def register_command(name, params, &block)
86
+ @commands.register_command(name, params, block)
87
+ end
88
+
89
+ # Method for triggering command
90
+ def run_command!
91
+ current_command_object.execute(current_params)
92
+ end
93
+
94
+ # Checks if the command is ready to be executed
95
+ def command_ready?
96
+ cmd = current_command_object
97
+ cmd.ready?(current_params)
98
+ end
99
+
100
+ # loads parameters from state
101
+ def current_params
102
+ @state[:params] || {}
103
+ end
104
+
105
+ # Loads command from state
106
+ def current_command_object
107
+ command = @state[:cmd]
108
+ @commands[command]
109
+ end
110
+
111
+ # returns the current_channel from where the message was sent
112
+ def current_channel
113
+ @state[:channel_id]
114
+ end
115
+
116
+ # stores the command into state
117
+ def command=(cmd)
118
+ log_debug('Message set as command', command: cmd)
119
+
120
+ @state[:cmd] = cmd.raw_message
121
+ @state[:params] = {}
122
+ end
123
+
124
+ # validates which is the following parameter required
125
+ def next_missing_param
126
+ current_command_object.next_missing_param(current_params)
127
+ end
128
+
129
+ # Sends a message to get the next parameter from the user
130
+ def ask_param(param)
131
+ log_debug('I\'m going to ask the next param', param: param)
132
+ @provider.send_message(current_channel,
133
+ "I need you to tell me #{param}")
134
+ @state[:requested_param] = param
135
+ end
136
+
137
+ # Stores a parameter into the status
138
+ def add_param(value)
139
+ log_debug('Received new param',
140
+ param: @state[:requested_param].to_sym,
141
+ value: value)
142
+
143
+ @state[:params][@state[:requested_param].to_sym] = value.raw_message
144
+ end
145
+
146
+ # Loads the state from storage
147
+ def load_state!(channel)
148
+ @state = load_state(channel)
149
+ end
150
+
151
+ # Private implementation for load message
152
+ def load_state(channel)
153
+ @factory.get(channel)
154
+ rescue Ant::Storage::Exceptions::ObjectNotFound
155
+ @factory.create(channel_id: channel, params: {})
156
+ end
157
+
158
+ # Saves the state into storage
159
+ def save_state!
160
+ @state.store
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ant
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
+ # Receives a list of params as symbols and the lambda with the block.
10
+ def initialize(params, block)
11
+ @params = params
12
+ @block = block
13
+ end
14
+
15
+ # Calls the block with the params list. Fails if there is a missing param
16
+ def execute(params)
17
+ raise 'NotReady' unless ready?(params)
18
+
19
+ @block.call(params)
20
+ end
21
+
22
+ # Checks if the params object given contains all the needed values
23
+ def ready?(current_params)
24
+ @params.all? { |key| current_params.key?(key) }
25
+ end
26
+
27
+ # Finds the first empty param from the given parameter
28
+ def next_missing_param(current_params)
29
+ @params.find { |key| !current_params.key?(key) }
30
+ end
31
+ end
32
+
33
+ # Wraps a collection of commands.
34
+ class CommandDefinition
35
+ def initialize
36
+ @commands = {}
37
+ end
38
+
39
+ # Stores an operation definition
40
+ def register_command(name, params, block)
41
+ @commands[name] = Command.new(params, block)
42
+ end
43
+
44
+ # Returns a command with the name
45
+ def [](name)
46
+ @commands[name]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ant/dry/daemon'
4
+ require 'ant/bot/adapters/base'
5
+
6
+ module Ant
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.start_with?('/')
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ant
4
+ module Bot
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,205 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ant-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gilberto Vargas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ant-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: ant-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: ant-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: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.16'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.16'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.5'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.5'
167
+ description: Provides a framework for building bots with ruby
168
+ email:
169
+ - tachoguitar@gmail.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - lib/ant/bot.rb
175
+ - lib/ant/bot/adapters/base.rb
176
+ - lib/ant/bot/adapters/debug.rb
177
+ - lib/ant/bot/adapters/telegram.rb
178
+ - lib/ant/bot/base.rb
179
+ - lib/ant/bot/command_definition.rb
180
+ - lib/ant/bot/message.rb
181
+ - lib/ant/bot/version.rb
182
+ homepage: https://github.com/tachomex/ant
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubygems_version: 3.0.3
202
+ signing_key:
203
+ specification_version: 4
204
+ summary: Provides a framework for building bots with ruby
205
+ test_files: []