kanal 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +38 -38
- data/README.md +135 -11
- data/lib/kanal/core/helpers/input_output_pair.rb +21 -0
- data/lib/kanal/core/helpers/parameter_finder_with_method_missing_mixin.rb +16 -4
- data/lib/kanal/core/helpers/response_execution_block.rb +7 -12
- data/lib/kanal/core/output/output.rb +2 -3
- data/lib/kanal/core/router/router.rb +58 -8
- data/lib/kanal/plugins/batteries/attachments/attachment.rb +9 -60
- data/lib/kanal/plugins/batteries/batteries_plugin.rb +108 -2
- data/lib/kanal/version.rb +1 -1
- data/lib/kanal.rb +1 -0
- data/lib/shortcuts.rb +9 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17fda67309b135e40e75787a8b2aba0908259bf25b55f3da4ad7170082090745
|
4
|
+
data.tar.gz: 0f751de312d93913c2f81e3b69eaee000359fa01327c873c64a128e46d88286c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8523d9eeed98f12107d9550cdc6e823707df37a817ed055229fa7b4b45c12d99e2786e83e1fd3be74acf98e4c01d2312aab37fa86f52869b7583ad3885c1def2
|
7
|
+
data.tar.gz: aa2272b6afcbf4f1dc655c7ed91e57df4caa0e046e4e420c0ddb5985d6cc7b470d05aa3bf372b656004aa5519e1071307d9de84ac1c4e0d6dfb4fe43a70ac6b4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] 2023-04-14
|
4
|
+
- new router method: `provide_output(output)` for providing output directly to consumer of `.output_ready(&block)` avoiding router and final output hook `:output_before_returned`
|
5
|
+
- new router method: `provide_output_with_input(output, input)` for providing output to consumer of `.output_ready(&block)` avoiding router but applying all hooks attached to the `:output_before_returned` (usually plugins attach to this hook to convert/transform output properties)
|
6
|
+
- internal: output hook :output_before_returned is executed after it was enqueued into the output_input_pair_queue. Meaning hook executed not right after processing respond blocks and creating output, but after it was enqueued to the queue which purpose is to ship output further into the `.output_ready(&block)` consumer. It was done so router public method `.provide_output_with_input(output, input)` was possible (avoiding router but not end hook)
|
7
|
+
|
8
|
+
## [0.4.3] 2023-03-30
|
9
|
+
- New condition for button pressed
|
10
|
+
- Input property button_pressed
|
11
|
+
|
3
12
|
## [0.4.2] 2023-03-22
|
4
13
|
- Adding any number of loggers can be now done with core.add_logger(l) - where l is logger that have same methods as default ruby logger (debug, warn, fatal, etc)
|
5
14
|
- To get logger into your class you can include Kanal::Core::Logging::Logger - this will give you method .logger to get logger
|
data/Gemfile.lock
CHANGED
@@ -1,94 +1,94 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
kanal (0.
|
4
|
+
kanal (0.5.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
ast (2.4.2)
|
10
10
|
backport (1.2.0)
|
11
|
-
benchmark (0.2.
|
11
|
+
benchmark (0.2.1)
|
12
12
|
diff-lcs (1.5.0)
|
13
13
|
docile (1.4.0)
|
14
14
|
e2mmap (0.1.0)
|
15
15
|
jaro_winkler (1.5.4)
|
16
|
-
json (2.6.
|
16
|
+
json (2.6.3)
|
17
17
|
kramdown (2.4.0)
|
18
18
|
rexml
|
19
19
|
kramdown-parser-gfm (1.1.0)
|
20
20
|
kramdown (~> 2.0)
|
21
|
-
mini_portile2 (2.8.
|
22
|
-
nokogiri (1.
|
21
|
+
mini_portile2 (2.8.1)
|
22
|
+
nokogiri (1.14.3)
|
23
23
|
mini_portile2 (~> 2.8.0)
|
24
24
|
racc (~> 1.4)
|
25
|
-
nokogiri (1.
|
25
|
+
nokogiri (1.14.3-x86_64-darwin)
|
26
26
|
racc (~> 1.4)
|
27
27
|
parallel (1.22.1)
|
28
|
-
parser (3.
|
28
|
+
parser (3.2.2.0)
|
29
29
|
ast (~> 2.4.1)
|
30
|
-
racc (1.6.
|
30
|
+
racc (1.6.2)
|
31
31
|
rainbow (3.1.1)
|
32
32
|
rake (13.0.6)
|
33
|
-
|
33
|
+
rbs (2.8.4)
|
34
|
+
regexp_parser (2.7.0)
|
34
35
|
reverse_markdown (2.1.1)
|
35
36
|
nokogiri
|
36
37
|
rexml (3.2.5)
|
37
|
-
rspec (3.
|
38
|
-
rspec-core (~> 3.
|
39
|
-
rspec-expectations (~> 3.
|
40
|
-
rspec-mocks (~> 3.
|
41
|
-
rspec-core (3.
|
42
|
-
rspec-support (~> 3.
|
43
|
-
rspec-expectations (3.
|
38
|
+
rspec (3.12.0)
|
39
|
+
rspec-core (~> 3.12.0)
|
40
|
+
rspec-expectations (~> 3.12.0)
|
41
|
+
rspec-mocks (~> 3.12.0)
|
42
|
+
rspec-core (3.12.1)
|
43
|
+
rspec-support (~> 3.12.0)
|
44
|
+
rspec-expectations (3.12.2)
|
44
45
|
diff-lcs (>= 1.2.0, < 2.0)
|
45
|
-
rspec-support (~> 3.
|
46
|
-
rspec-mocks (3.
|
46
|
+
rspec-support (~> 3.12.0)
|
47
|
+
rspec-mocks (3.12.5)
|
47
48
|
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.
|
49
|
-
rspec-support (3.
|
50
|
-
rubocop (1.
|
49
|
+
rspec-support (~> 3.12.0)
|
50
|
+
rspec-support (3.12.0)
|
51
|
+
rubocop (1.50.0)
|
51
52
|
json (~> 2.3)
|
52
53
|
parallel (~> 1.10)
|
53
|
-
parser (>= 3.
|
54
|
+
parser (>= 3.2.0.0)
|
54
55
|
rainbow (>= 2.2.2, < 4.0)
|
55
56
|
regexp_parser (>= 1.8, < 3.0)
|
56
57
|
rexml (>= 3.2.5, < 4.0)
|
57
|
-
rubocop-ast (>= 1.
|
58
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
58
59
|
ruby-progressbar (~> 1.7)
|
59
|
-
unicode-display_width (>=
|
60
|
-
rubocop-ast (1.
|
61
|
-
parser (>= 3.
|
60
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
61
|
+
rubocop-ast (1.28.0)
|
62
|
+
parser (>= 3.2.1.0)
|
62
63
|
ruby-debug-ide (0.7.3)
|
63
64
|
rake (>= 0.8.1)
|
64
|
-
ruby-progressbar (1.
|
65
|
-
simplecov (0.
|
65
|
+
ruby-progressbar (1.13.0)
|
66
|
+
simplecov (0.22.0)
|
66
67
|
docile (~> 1.1)
|
67
68
|
simplecov-html (~> 0.11)
|
68
69
|
simplecov_json_formatter (~> 0.1)
|
69
70
|
simplecov-html (0.12.3)
|
70
71
|
simplecov_json_formatter (0.1.4)
|
71
|
-
solargraph (0.
|
72
|
+
solargraph (0.49.0)
|
72
73
|
backport (~> 1.2)
|
73
74
|
benchmark
|
74
|
-
bundler (
|
75
|
+
bundler (~> 2.0)
|
75
76
|
diff-lcs (~> 1.4)
|
76
77
|
e2mmap
|
77
78
|
jaro_winkler (~> 1.5)
|
78
79
|
kramdown (~> 2.3)
|
79
80
|
kramdown-parser-gfm (~> 1.1)
|
80
81
|
parser (~> 3.0)
|
81
|
-
|
82
|
-
|
82
|
+
rbs (~> 2.0)
|
83
|
+
reverse_markdown (~> 2.0)
|
84
|
+
rubocop (~> 1.38)
|
83
85
|
thor (~> 1.0)
|
84
86
|
tilt (~> 2.0)
|
85
87
|
yard (~> 0.9, >= 0.9.24)
|
86
88
|
thor (1.2.1)
|
87
|
-
tilt (2.0
|
88
|
-
unicode-display_width (2.2
|
89
|
-
|
90
|
-
yard (0.9.28)
|
91
|
-
webrick (~> 1.7.0)
|
89
|
+
tilt (2.1.0)
|
90
|
+
unicode-display_width (2.4.2)
|
91
|
+
yard (0.9.33)
|
92
92
|
|
93
93
|
PLATFORMS
|
94
94
|
ruby
|
@@ -105,4 +105,4 @@ DEPENDENCIES
|
|
105
105
|
yard
|
106
106
|
|
107
107
|
BUNDLED WITH
|
108
|
-
2.
|
108
|
+
2.4.5
|
data/README.md
CHANGED
@@ -1,18 +1,142 @@
|
|
1
|
-
# Kanal
|
2
1
|
|
3
|
-
|
2
|
+
# Kanal
|
4
3
|
|
5
|
-
|
4
|
+
Welcome to Kanal!
|
5
|
+
|
6
|
+
Kanal is a platform to create chat-bots with it's own DSL. As a key feature Kanal provides router which is configured by you, the developer. Configuration of router implies setting specific responses to specific inputs. Router can consume input (text, images, audio, files) and prepare response (or several responses) to it to be processed further. Configured router defines chat-bot's communication logic that can be used by different types of chat-platforms (Telegram, Discord, etc.)
|
7
|
+
|
8
|
+
Core functionality of Kanal can be extended with plugins. For example, plugins can provide database to store data; user system that allows storing and working with data of end-users of chat-bot; integration with telegram or discord to receive messages from end-users and responses to be sent to them. Kanal has Batteries plugin that allows input and output to have text and also image, audio or file attached.
|
9
|
+
|
10
|
+
## Overview of Kanal components
|
11
|
+
#### Core
|
12
|
+
Core is is a key Kanal component. To create the Kanal app is to create Core.
|
13
|
+
```
|
14
|
+
core = Kanal::Core::Core.new
|
15
|
+
```
|
16
|
+
#### Input and Output
|
17
|
+
Input is an object representing information received from end-user. To store data inside Input and access it later you need to register parameter for it. Then you can pass your data to it.
|
18
|
+
```
|
19
|
+
core.register_input_parameter :test_parameter
|
20
|
+
input = core.create_input
|
21
|
+
input.test_parameter = "test_value"
|
22
|
+
```
|
23
|
+
Batteries plugin provides pre-made parameters, such as body and source.
|
24
|
+
```
|
25
|
+
input.body = <Your string>
|
26
|
+
# Source should be of Symbol type
|
27
|
+
input.source = :telegram
|
28
|
+
input.audio =
|
29
|
+
input.image =
|
30
|
+
input.file =
|
31
|
+
```
|
32
|
+
TODO: Describe image, audio and file parameters. Are they supposed to contain url strings?
|
33
|
+
|
34
|
+
Output is an object representing information ready to be sent to end-user. As with Input, you can register your own parameters. Batteries provides same parameters for Output as for Input.
|
35
|
+
#### Conditions
|
36
|
+
Condition is a true/false blocks that will tell the router if particular route should be used. Conditions are registered within the `condition pack` that has specific name.
|
37
|
+
```
|
38
|
+
core.add_condition_pack :contains_day_of_week do
|
39
|
+
add_condition :friday
|
40
|
+
met? do |input, core, argument|
|
41
|
+
# Rememer how you registered .body parameter of input? It will be accessible in condition here!
|
42
|
+
input.body.include? "friday"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
add_condition :monday
|
47
|
+
met? do |input, core, argument|
|
48
|
+
input.body.include? "monday"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
```
|
53
|
+
#### Router
|
54
|
+
Router is a collection of responses to specific conditions defined by user. Upon meeting the condition router will create output (or outputs). Using previously made conditions we can specify what operation will be performed and what the output (outputs) will contain.
|
55
|
+
```
|
56
|
+
core.router.configure do
|
57
|
+
on :contains_day_of_week :friday do
|
58
|
+
respond do
|
59
|
+
body "What, already?"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
on :contains_day_of_week :tuesday do
|
64
|
+
respond do
|
65
|
+
body "Start of the week huh"
|
66
|
+
end
|
67
|
+
|
68
|
+
respond_async do
|
69
|
+
<http request or database call here>
|
70
|
+
body "I will work tirelessly!"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
In respond block you can do anything besides setting output parameters.
|
76
|
+
|
77
|
+
_It is advised to make database calls, http requests or other time-consuming operations in respond_async block._
|
78
|
+
|
79
|
+
Router has built-in default response for when none of the conditions are met. You can set your own default response.
|
80
|
+
```
|
81
|
+
core.router.default_response do
|
82
|
+
body "Custom default message here"
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Router has built-in error response for when there is an error during constructing of output. You can set your own error response.
|
87
|
+
```
|
88
|
+
core.router.error_response do
|
89
|
+
body "Custom error message here"
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Hooks
|
94
|
+
Hooks can be used to intercept the execution flow in specific places in your code and do something with data provided to it. Hooks are registered in the Core. You can attach to hook, specifying arguments and block of code to be executed. When the hook will be called, the argument will be passed to provided block and it will be executed.
|
95
|
+
```
|
96
|
+
core.hook_storage.register :on_something
|
97
|
+
val = nil
|
98
|
+
hooks.attach :on_something do |value|
|
99
|
+
val = value
|
100
|
+
end
|
101
|
+
hooks.call :on_something, "testy"
|
102
|
+
puts val # "testy"
|
103
|
+
```
|
104
|
+
|
105
|
+
By default core has 3 hooks.
|
106
|
+
```
|
107
|
+
:input_just_created # input
|
108
|
+
:input_before_router # input
|
109
|
+
:output_before_returned # input, output
|
110
|
+
```
|
111
|
+
|
112
|
+
#### Attachments
|
113
|
+
Batteries plugin provides Attachments functionality.
|
114
|
+
```
|
115
|
+
attachment = ::Attachment.new "https://website.com/filename.jpg"
|
116
|
+
# Get the url value
|
117
|
+
attachment.url # "https://website.com/filename.jpg"
|
118
|
+
# Get url file extension
|
119
|
+
attachment.extension # "jpg"
|
120
|
+
# Different helper methods for different multimedia types.
|
121
|
+
attachment.jpg? # True
|
122
|
+
attachment.image? # True
|
123
|
+
attachment.mp3? # False
|
124
|
+
attachment.audio? # False
|
125
|
+
```
|
126
|
+
#### Interfaces
|
127
|
+
#### Plugins
|
128
|
+
#### Interfaces
|
129
|
+
#### Plugins
|
6
130
|
|
7
131
|
## Installation
|
8
132
|
|
9
133
|
Install the gem and add to the application's Gemfile by executing:
|
10
134
|
|
11
|
-
|
135
|
+
$ bundle add kanal
|
12
136
|
|
13
137
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
14
138
|
|
15
|
-
|
139
|
+
$ gem install kanal
|
16
140
|
|
17
141
|
## Usage
|
18
142
|
|
@@ -20,13 +144,13 @@ TODO: Write usage instructions here
|
|
20
144
|
|
21
145
|
## TODO
|
22
146
|
|
23
|
-
- [DONE] ~provide default response for branch with subbranches because default response could be handy~
|
24
|
-
|
147
|
+
- [DONE] ~provide default response for branch with subbranches because default response could be handy~
|
148
|
+
Provided with the :flow condition pack with condition :any
|
25
149
|
- [DONE] ~rework hooks storage, make hooks without arguments validation~
|
26
|
-
- provide default logger with base class. this logger service should be used by every other service/plugin etc
|
27
|
-
- provide default response on error, when router node fails with error
|
28
|
-
- [DONE] ~provide :source condition for :source~
|
29
|
-
|
150
|
+
- ~provide default logger with base class. this logger service should be used by every other service/plugin etc~
|
151
|
+
- ~provide default response on error, when router node fails with error~
|
152
|
+
- [DONE] ~provide :source condition for :source~
|
153
|
+
Created :source condition pack
|
30
154
|
- Allow to "append" conditions to condition packs
|
31
155
|
|
32
156
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kanal
|
4
|
+
module Core
|
5
|
+
module Helpers
|
6
|
+
#
|
7
|
+
# Objects of this class are constructed by router to be put into #router.input_output_pair_queue
|
8
|
+
# It is needed so we don't keep input directly in output as a parameter
|
9
|
+
# Upon queueing these objects processed in item_added hook, for which input might be needed
|
10
|
+
#
|
11
|
+
class InputOutputPair
|
12
|
+
attr_reader :input, :output
|
13
|
+
|
14
|
+
def initialize(input, output)
|
15
|
+
@input = input
|
16
|
+
@output = output
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../logging/composite_logger"
|
4
|
+
|
3
5
|
module Kanal
|
4
6
|
module Core
|
5
7
|
module Helpers
|
@@ -8,22 +10,32 @@ module Kanal
|
|
8
10
|
# get(name)
|
9
11
|
# transforms unknown methods to setters/getters for parameters
|
10
12
|
module ParameterFinderWithMethodMissingMixin
|
11
|
-
|
13
|
+
include Logging::Logger
|
14
|
+
|
15
|
+
def method_missing(symbol, *args, &block)
|
16
|
+
if block && !args.first.nil?
|
17
|
+
logger.error "Block and arg given simultaneously. Parameter name: '#{symbol.to_s}, arg: #{args.first}'"
|
18
|
+
|
19
|
+
raise "Block and arg given simultaneously for parameter #{symbol.to_s}"
|
20
|
+
end
|
21
|
+
|
12
22
|
parameter_name = symbol.to_s
|
13
23
|
parameter_name.sub! "=", ""
|
14
24
|
|
15
25
|
parameter_name = parameter_name.to_sym
|
16
26
|
|
27
|
+
value = block_given? ? block : args.first
|
28
|
+
|
17
29
|
# standard workflow with settings properties with
|
18
30
|
# input.prop = 123
|
19
31
|
if symbol.to_s.include? "="
|
20
|
-
@parameter_bag.set parameter_name,
|
21
|
-
elsif !args.empty?
|
32
|
+
@parameter_bag.set parameter_name, value
|
33
|
+
elsif !args.empty? || block_given?
|
22
34
|
# this approach can be used also in dsl
|
23
35
|
# like that
|
24
36
|
# setters: prop value
|
25
37
|
# getters: prop
|
26
|
-
@parameter_bag.set parameter_name,
|
38
|
+
@parameter_bag.set parameter_name, value
|
27
39
|
# means it is used as setter in dsl,
|
28
40
|
# method call with argument
|
29
41
|
else
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative "../output/output"
|
4
4
|
require_relative "../logging/logger"
|
5
|
+
require_relative "../helpers/input_output_pair"
|
5
6
|
|
6
7
|
module Kanal
|
7
8
|
module Core
|
@@ -19,16 +20,16 @@ module Kanal
|
|
19
20
|
@error_node = error_node
|
20
21
|
end
|
21
22
|
|
22
|
-
def execute(core,
|
23
|
+
def execute(core, input_output_pair_queue)
|
23
24
|
if response_block.async?
|
24
|
-
# NOTE: Thread doesnt just die here - it's execution is continued in
|
25
|
+
# NOTE: Thread doesnt just die here - it's execution is continued in input_output_pair_queue.enqueue in router
|
25
26
|
# then :item_queued hook is called inside and subsequently output_ready_block gets called in this thread
|
26
27
|
# TODO: be aware that this can cause unexpected behaviour. Maybe think how to rework it.
|
27
28
|
Thread.new do
|
28
|
-
|
29
|
+
input_output_pair_queue.enqueue InputOutputPair.new(@input, construct_output(core))
|
29
30
|
end
|
30
31
|
else
|
31
|
-
|
32
|
+
input_output_pair_queue.enqueue InputOutputPair.new(@input, construct_output(core))
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -37,18 +38,16 @@ module Kanal
|
|
37
38
|
def construct_output(core)
|
38
39
|
logger.info "Constructing output for input ##{input.__id__}"
|
39
40
|
|
40
|
-
output = Output::Output.new core.output_parameter_registrator,
|
41
|
+
output = Output::Output.new core.output_parameter_registrator, core
|
41
42
|
|
42
43
|
begin
|
43
44
|
core.hooks.call :output_just_created, input, output
|
44
45
|
|
45
46
|
output.instance_eval(&@response_block.block)
|
46
|
-
|
47
|
-
core.hooks.call :output_before_returned, input, output
|
48
47
|
rescue => e
|
49
48
|
logger.error "Failed to construct output for input ##{input.__id__}. Error: '#{e}'"
|
50
49
|
|
51
|
-
output = Output::Output.new core.output_parameter_registrator,
|
50
|
+
output = Output::Output.new core.output_parameter_registrator, core
|
52
51
|
|
53
52
|
error_node = @error_node || @default_error_node
|
54
53
|
|
@@ -56,16 +55,12 @@ module Kanal
|
|
56
55
|
|
57
56
|
begin
|
58
57
|
output.instance_eval(&error_node.response_blocks.first.block)
|
59
|
-
|
60
|
-
core.hooks.call :output_before_returned, input, output
|
61
58
|
rescue => e
|
62
59
|
logger.error "Failed to construct error response for input ##{input.__id__}. Error: '#{e}'"
|
63
60
|
|
64
61
|
logger.info "Trying to construct default error response for input ##{input.__id__}"
|
65
62
|
|
66
63
|
output.instance_eval(&@default_error_node.response_blocks.first.block)
|
67
|
-
|
68
|
-
core.hooks.call :output_before_returned, input, output
|
69
64
|
end
|
70
65
|
end
|
71
66
|
|
@@ -12,10 +12,9 @@ module Kanal
|
|
12
12
|
include Helpers
|
13
13
|
include Helpers::ParameterFinderWithMethodMissingMixin
|
14
14
|
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :core
|
16
16
|
|
17
|
-
def initialize(parameter_registrator,
|
18
|
-
@input = input
|
17
|
+
def initialize(parameter_registrator, core)
|
19
18
|
@core = core
|
20
19
|
@parameter_bag = ParameterBagWithRegistrator.new parameter_registrator
|
21
20
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require_relative "./router_node"
|
4
4
|
require_relative "../helpers/queue"
|
5
5
|
require_relative "../helpers/response_execution_block"
|
6
|
+
require_relative "../helpers/input_output_pair"
|
6
7
|
require_relative "../logging/composite_logger"
|
7
8
|
|
8
9
|
module Kanal
|
@@ -35,20 +36,30 @@ module Kanal
|
|
35
36
|
end
|
36
37
|
@error_node = nil
|
37
38
|
@response_execution_queue = Queue.new
|
38
|
-
@
|
39
|
+
@input_output_pair_queue = Queue.new
|
39
40
|
@output_ready_block = nil
|
40
41
|
@core.hooks.register(:output_ready) # arg
|
41
42
|
|
42
43
|
_this = self
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
_input_output_pair_queue = @input_output_pair_queue
|
45
|
+
# Attaching to the queue hook to capture enqueued output and move it
|
46
|
+
# forward in the workflow: apply hooks in :output_before_ready and pass it to
|
47
|
+
# the end consumer which uses .output_ready(&block)
|
48
|
+
@input_output_pair_queue.hooks.attach :item_queued do |input_output_pair|
|
49
|
+
_this.logger.info "Calling output_ready block for input ##{input_output_pair.input.__id__} and output #{input_output_pair.output.__id__}. Output body is: '#{input_output_pair.output.body}'"
|
46
50
|
|
47
51
|
begin
|
48
|
-
_this.
|
49
|
-
|
52
|
+
_this.core.hooks.call :output_before_returned, input_output_pair.input, input_output_pair.output
|
53
|
+
rescue => e
|
54
|
+
logger.error "Error during output_before_returned call: #{e}"
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
_this.output_ready_block.call input_output_pair.output
|
60
|
+
_input_output_pair_queue.remove(input_output_pair)
|
50
61
|
rescue
|
51
|
-
|
62
|
+
_input_output_pair_queue.remove(input_output_pair)
|
52
63
|
raise "Error in output_ready block!"
|
53
64
|
end
|
54
65
|
end
|
@@ -85,6 +96,45 @@ module Kanal
|
|
85
96
|
@error_node.respond(&block)
|
86
97
|
end
|
87
98
|
|
99
|
+
# Method allows to pass output with input directly to the end step,
|
100
|
+
# when outputs are processed after router. Meaning it will not go
|
101
|
+
# through router, but will have all hooks in :output_before_returned executed for it.
|
102
|
+
# Input is needed for hooks to work properly.
|
103
|
+
#
|
104
|
+
# WARNING: this method AVOIDS router whatsoever
|
105
|
+
# Why use this method? If you want router to give out (through output_ready(&block) method) output without
|
106
|
+
# passing through router but with hooks called (many plugins use hooks to alter the output) - you
|
107
|
+
# use this method.
|
108
|
+
#
|
109
|
+
# This method adds input-output pair into input_output_pair_queue for it to be processed in item_queued hook
|
110
|
+
#
|
111
|
+
# param [Kanal::Core::Output::Output] output <description>
|
112
|
+
# param [Kanal::Core::Input::Input] input <description>
|
113
|
+
#
|
114
|
+
def provide_output_with_input(output, input)
|
115
|
+
@input_output_pair_queue.enqueue InputOutputPair.new input, output
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Method allows to pass output directly to the end output
|
120
|
+
# consumer (code which uses .output_ready(&block). Without passing router and
|
121
|
+
# without passing hooks attached to :output_before_ready
|
122
|
+
#
|
123
|
+
# WARNING: output provided via this method will avoid any alteration by the
|
124
|
+
# router inner machinery and will go directly to the output consumer
|
125
|
+
#
|
126
|
+
# Why use this method? If you want to pass output with your manually defined
|
127
|
+
# properties to the output consumer (which uses .output_ready(&block))
|
128
|
+
# without any alternations by the router system.
|
129
|
+
#
|
130
|
+
# This method calls output_ready_block on pre-formed output right away
|
131
|
+
#
|
132
|
+
# param [Kanal::Core::Output] output <description>
|
133
|
+
#
|
134
|
+
def provide_output(output)
|
135
|
+
@output_ready_block.call output
|
136
|
+
end
|
137
|
+
|
88
138
|
# Main method for creating output(s) if it is found or going to default output
|
89
139
|
def consume_input(input)
|
90
140
|
logger.info "Consuming input #{input.__id__}."
|
@@ -139,7 +189,7 @@ module Kanal
|
|
139
189
|
until @response_execution_queue.empty?
|
140
190
|
response_execution = @response_execution_queue.dequeue
|
141
191
|
|
142
|
-
response_execution.execute core, @
|
192
|
+
response_execution.execute core, @input_output_pair_queue
|
143
193
|
end
|
144
194
|
end
|
145
195
|
|
@@ -15,76 +15,25 @@ module Kanal
|
|
15
15
|
@url = url
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def mp3?
|
23
|
-
extension == "mp3"
|
24
|
-
end
|
25
|
-
|
26
|
-
def wav?
|
27
|
-
extension == "wav"
|
28
|
-
end
|
29
|
-
|
30
|
-
def ogg?
|
31
|
-
extension == "ogg"
|
32
|
-
end
|
33
|
-
|
34
|
-
def document?
|
35
|
-
doc? || docx? || odf?
|
36
|
-
end
|
37
|
-
|
38
|
-
def doc?
|
39
|
-
extension == "doc"
|
40
|
-
end
|
41
|
-
|
42
|
-
def docx?
|
43
|
-
extension == "docx"
|
44
|
-
end
|
45
|
-
|
46
|
-
def odf?
|
47
|
-
extension == "odf"
|
18
|
+
# Extension checks like jpg?, mp3?, mp4?, doc? etc. fall here
|
19
|
+
def method_missing(method)
|
20
|
+
extension == method.to_s.delete("?")
|
48
21
|
end
|
49
22
|
|
50
23
|
def image?
|
51
|
-
jpg
|
52
|
-
end
|
53
|
-
|
54
|
-
def jpg?
|
55
|
-
extension == "jpg"
|
56
|
-
end
|
57
|
-
|
58
|
-
def jpeg?
|
59
|
-
extension == "jpeg"
|
60
|
-
end
|
61
|
-
|
62
|
-
def png?
|
63
|
-
extension == "png"
|
24
|
+
[jpg?, jpeg?, png?, bmp?, gif?].any?
|
64
25
|
end
|
65
26
|
|
66
|
-
def
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
def gif?
|
71
|
-
extension == "gif"
|
27
|
+
def audio?
|
28
|
+
[mp3?, wav?, ogg?].any?
|
72
29
|
end
|
73
30
|
|
74
31
|
def video?
|
75
|
-
mp4
|
76
|
-
end
|
77
|
-
|
78
|
-
def mp4?
|
79
|
-
extension == "mp4"
|
32
|
+
[mp4?, mov?, mkv?].any?
|
80
33
|
end
|
81
34
|
|
82
|
-
def
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
def mkv?
|
87
|
-
extension == "mkv"
|
35
|
+
def document?
|
36
|
+
[doc?, docx?, odf?].any?
|
88
37
|
end
|
89
38
|
|
90
39
|
#
|
@@ -9,6 +9,8 @@ module Kanal
|
|
9
9
|
module Batteries
|
10
10
|
# Plugin with some batteries like .body property etc
|
11
11
|
class BatteriesPlugin < Core::Plugins::Plugin
|
12
|
+
|
13
|
+
|
12
14
|
def name
|
13
15
|
:batteries
|
14
16
|
end
|
@@ -19,6 +21,8 @@ module Kanal
|
|
19
21
|
flow_batteries core
|
20
22
|
attachments_batteries core
|
21
23
|
keyboard_batteries core
|
24
|
+
username_batteries core
|
25
|
+
button_batteries core
|
22
26
|
end
|
23
27
|
|
24
28
|
def flow_batteries(core)
|
@@ -117,11 +121,95 @@ module Kanal
|
|
117
121
|
def attachments_batteries(core)
|
118
122
|
core.register_input_parameter :image
|
119
123
|
core.register_input_parameter :audio
|
120
|
-
core.register_input_parameter :
|
124
|
+
core.register_input_parameter :video
|
125
|
+
core.register_input_parameter :document
|
121
126
|
|
122
127
|
core.register_output_parameter :image
|
123
128
|
core.register_output_parameter :audio
|
124
|
-
core.register_output_parameter :
|
129
|
+
core.register_output_parameter :video
|
130
|
+
core.register_output_parameter :document
|
131
|
+
|
132
|
+
_this = self
|
133
|
+
|
134
|
+
core.add_condition_pack :image do
|
135
|
+
add_condition :exists do
|
136
|
+
met? do |input, _core, _argument|
|
137
|
+
!input.image.nil?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
add_condition :is do
|
142
|
+
with_argument
|
143
|
+
|
144
|
+
met? do |input, _, argument|
|
145
|
+
if input.image.image? && input.image.send((argument.to_s + "?").to_sym)
|
146
|
+
true
|
147
|
+
else
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
core.add_condition_pack :audio do
|
155
|
+
add_condition :exists do
|
156
|
+
met? do |input, _core, _argument|
|
157
|
+
!input.audio.nil?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
add_condition :is do
|
162
|
+
with_argument
|
163
|
+
|
164
|
+
met? do |input, _, argument|
|
165
|
+
if input.audio.audio? && input.audio.send((argument.to_s + "?").to_sym)
|
166
|
+
true
|
167
|
+
else
|
168
|
+
false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
core.add_condition_pack :video do
|
175
|
+
add_condition :exists do
|
176
|
+
met? do |input, _core, _argument|
|
177
|
+
!input.video.nil?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
add_condition :is do
|
182
|
+
with_argument
|
183
|
+
|
184
|
+
met? do |input, _, argument|
|
185
|
+
if input.video.video? && input.video.send((argument.to_s + "?").to_sym)
|
186
|
+
true
|
187
|
+
else
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
core.add_condition_pack :document do
|
195
|
+
add_condition :exists do
|
196
|
+
met? do |input, _core, _argument|
|
197
|
+
!input.document.nil?
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
add_condition :is do
|
202
|
+
with_argument
|
203
|
+
|
204
|
+
met? do |input, _, argument|
|
205
|
+
if input.document.document? && input.document.send((argument.to_s + "?").to_sym)
|
206
|
+
true
|
207
|
+
else
|
208
|
+
false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
125
213
|
end
|
126
214
|
|
127
215
|
def keyboard_batteries(core)
|
@@ -131,6 +219,24 @@ module Kanal
|
|
131
219
|
output.keyboard = Keyboard.new
|
132
220
|
end
|
133
221
|
end
|
222
|
+
|
223
|
+
def username_batteries(core)
|
224
|
+
core.register_input_parameter :username
|
225
|
+
end
|
226
|
+
|
227
|
+
def button_batteries(core)
|
228
|
+
core.register_input_parameter :button_pressed
|
229
|
+
|
230
|
+
core.add_condition_pack :button do
|
231
|
+
add_condition :pressed do
|
232
|
+
with_argument
|
233
|
+
|
234
|
+
met? do |input, _, argument|
|
235
|
+
input.button_pressed == argument
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
134
240
|
end
|
135
241
|
end
|
136
242
|
end
|
data/lib/kanal/version.rb
CHANGED
data/lib/kanal.rb
CHANGED
data/lib/shortcuts.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kanal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- idchlife
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Thanks to the core library, ecosystem of Kanal tools can be extendted
|
14
14
|
to use with input-output bot-like behaviour, with routing
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/kanal/core/conditions/condition_storage.rb
|
38
38
|
- lib/kanal/core/core.rb
|
39
39
|
- lib/kanal/core/helpers/condition_finder.rb
|
40
|
+
- lib/kanal/core/helpers/input_output_pair.rb
|
40
41
|
- lib/kanal/core/helpers/parameter_bag.rb
|
41
42
|
- lib/kanal/core/helpers/parameter_bag_with_registrator.rb
|
42
43
|
- lib/kanal/core/helpers/parameter_finder_with_method_missing_mixin.rb
|
@@ -63,6 +64,7 @@ files:
|
|
63
64
|
- lib/kanal/plugins/batteries/batteries_plugin.rb
|
64
65
|
- lib/kanal/plugins/batteries/keyboard.rb
|
65
66
|
- lib/kanal/version.rb
|
67
|
+
- lib/shortcuts.rb
|
66
68
|
- sig/kanal.rbs
|
67
69
|
- sig/kanal/core/conditions/condition_pack.rbs
|
68
70
|
- sig/kanal/core/conditions/condition_storage.rbs
|