telegram_workflow 1.2.0 → 1.3.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/.gitignore +1 -0
- data/README.md +17 -0
- data/example/Gemfile +2 -0
- data/example/README.md +79 -0
- data/example/actions/create_appointment.rb +56 -0
- data/example/actions/list_actions.rb +28 -0
- data/example/actions/list_appointments.rb +13 -0
- data/example/actions/select_doctor.rb +19 -0
- data/example/actions/start.rb +9 -0
- data/example/client.rb +5 -0
- data/example/environment.rb +13 -0
- data/example/flow.jpg +0 -0
- data/example/main.rb +17 -0
- data/lib/telegram_workflow.rb +1 -0
- data/lib/telegram_workflow/client.rb +5 -2
- data/lib/telegram_workflow/errors.rb +6 -0
- data/lib/telegram_workflow/input_file.rb +2 -0
- data/lib/telegram_workflow/version.rb +1 -1
- data/lib/telegram_workflow/workflow.rb +1 -0
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f33398c3b33397236392a5b645e39b58827df8e453727fd06496ce0b4479e11
|
4
|
+
data.tar.gz: 9155e202748c8242adc4f4789623b8c42156658616e545d1aafd2aa4f2f776f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f85e7ab9b297deab054185d03863c8183bf7fbd6a7f1325ec612fa0042ee0999a700e80d91cb5cbed3b945605595904ec1716192b341d39f2402df805ea69f1
|
7
|
+
data.tar.gz: 67bf8b6d8abc59f26c2e302f989aaa17777303bc3efdfb29844686d35f62994a2257557e7838188292e2fbbecc7eb7d1ad872c3f123eb9eb1b61b686b046a5ba
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -140,6 +140,19 @@ client.send_location latitude: 40.748, longitude: -73.985, live_period: 120
|
|
140
140
|
|
141
141
|
`chat_id` parameter should be omitted.
|
142
142
|
|
143
|
+
Use `TelegramWorkflow::InputFile` to upload a file:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
client.send_photo photo: TelegramWorkflow::InputFile.new("/Users/telegram/images/image1.jpg")
|
147
|
+
```
|
148
|
+
|
149
|
+
`filename` and `content_type` fields can be customized:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
file = StringIO.new("hello!")
|
153
|
+
client.send_document document: TelegramWorkflow::InputFile.new(file, filename: "hello.txt")
|
154
|
+
```
|
155
|
+
|
143
156
|
### session
|
144
157
|
|
145
158
|
This is a persistent store to save the data associated with a user, e.g. current user's id, some settings or anything you would store in a session in a regular web application.
|
@@ -381,6 +394,10 @@ end
|
|
381
394
|
|
382
395
|
As you can see, testing utility starts the flow automatically, calling `initial` step on `described_class`.
|
383
396
|
|
397
|
+
## Example
|
398
|
+
|
399
|
+
Check out an example of a bot under [example](example) folder.
|
400
|
+
|
384
401
|
## Development
|
385
402
|
|
386
403
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/example/Gemfile
ADDED
data/example/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
## Description
|
2
|
+
|
3
|
+
This is a fully working example of a bot which allows you to book an appointment with a doctor.
|
4
|
+
|
5
|
+
The following diagram will help you understand the bot's flow:
|
6
|
+
<p>
|
7
|
+
<img src="https://github.com/rsamoilov/telegram_workflow/blob/master/example/flow.jpg" width="400">
|
8
|
+
</p>
|
9
|
+
|
10
|
+
## Running the bot
|
11
|
+
|
12
|
+
First, open [main.rb](main.rb) and configure the bot with your API token:
|
13
|
+
|
14
|
+
```diff
|
15
|
+
TelegramWorkflow.configure do |config|
|
16
|
+
- config.api_token = <YOUR_TOKEN>
|
17
|
+
+ config.api_token = "123456780:ABCDE_my-token"
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Next, run the bot:
|
22
|
+
|
23
|
+
```
|
24
|
+
bundle
|
25
|
+
ruby main.rb
|
26
|
+
```
|
27
|
+
|
28
|
+
## Configuration
|
29
|
+
|
30
|
+
Every Bot workflow begins with **`on_message`** callback in a `start_action`.
|
31
|
+
There's no need to store current user data in session in this example, so we simply redirect to the `ListActions` action, which will be our "main" action.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class Actions::Start < TelegramWorkflow::Action
|
35
|
+
def initial
|
36
|
+
on_message do
|
37
|
+
redirect_to Actions::ListActions
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Next, the Telegram client can be customized. We want to use Telegram's [InlineKeyboard](https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating) to provide a user with a list of available actions.
|
44
|
+
|
45
|
+
Let's encapsulate this inside our custom client class:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Client < TelegramWorkflow::Client
|
49
|
+
def send_actions(message, actions)
|
50
|
+
send_message text: message, reply_markup: { inline_keyboard: actions }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Now, let's configure the gem:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
TelegramWorkflow.configure do |config|
|
59
|
+
config.start_action = Actions::Start
|
60
|
+
config.client = Client
|
61
|
+
config.session_store = TelegramWorkflow::Stores::File.new
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
The last configuration parameter here is `session_store`. We are using `TelegramWorkflow::Stores::File` - a built-in implementation of persistent file store.
|
66
|
+
|
67
|
+
[getUpdates](https://core.telegram.org/bots/api#getupdates) method is used in this example to receive the updates:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
TelegramWorkflow.updates.each do |params|
|
71
|
+
TelegramWorkflow.process(params)
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
After a user has sent a message, `TelegramWorkflow.process` will initialize the last processed action and step and then call `on_message` callback on it.
|
76
|
+
|
77
|
+
## Actions
|
78
|
+
|
79
|
+
Check out the bot's code under [actions](actions) folder.
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# this is an action to create an appointment;
|
3
|
+
# each method represents one of the steps for appointment creation;
|
4
|
+
# for the sake of simplicity, all created appointments are stored in the session
|
5
|
+
#
|
6
|
+
class Actions::CreateAppointment < TelegramWorkflow::Action
|
7
|
+
def initial
|
8
|
+
on_redirect do
|
9
|
+
client.send_message text: "Enter patient's name:"
|
10
|
+
end
|
11
|
+
|
12
|
+
on_message do
|
13
|
+
flash[:name] = params.message_text
|
14
|
+
redirect_to :reason
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def reason
|
19
|
+
on_redirect do
|
20
|
+
client.send_message text: "What is the reason for visit?"
|
21
|
+
end
|
22
|
+
|
23
|
+
on_message do
|
24
|
+
flash[:reason] = params.message_text
|
25
|
+
redirect_to :date
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def date
|
30
|
+
on_redirect do
|
31
|
+
client.send_message text: "What date works best for you?"
|
32
|
+
end
|
33
|
+
|
34
|
+
on_message do
|
35
|
+
date = Date.parse(params.message_text) rescue nil
|
36
|
+
|
37
|
+
# there's no redirect in case the date is invalid;
|
38
|
+
# this means that next time a user sends a message, the current action will be executed again
|
39
|
+
if date
|
40
|
+
flash[:date] = date
|
41
|
+
redirect_to :done
|
42
|
+
else
|
43
|
+
client.send_message text: "Invalid date format. Please try again."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# `specialty` parameter is added to flash in previous `Actions::SelectDoctor` action
|
49
|
+
def done
|
50
|
+
on_redirect do
|
51
|
+
(session[:appointments] ||= []) << flash.slice(:name, :reason, :date, :specialty)
|
52
|
+
client.send_message text: "Your appointment has been created!"
|
53
|
+
redirect_to Actions::ListActions
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Actions::ListActions < TelegramWorkflow::Action
|
2
|
+
def initial
|
3
|
+
on_redirect do
|
4
|
+
available_actions = [
|
5
|
+
[{ text: "Make an appointment", callback_data: "create" }]
|
6
|
+
]
|
7
|
+
|
8
|
+
if session[:appointments]&.any?
|
9
|
+
available_actions.unshift [{ text: "List my appointments", callback_data: "list" }]
|
10
|
+
end
|
11
|
+
|
12
|
+
# this is the customized client object
|
13
|
+
client.send_actions "Select an action:", available_actions
|
14
|
+
end
|
15
|
+
|
16
|
+
# `params.callback_data` here will be one of the identifiers defined as `callback_data` in `available_actions` array
|
17
|
+
# refer to https://core.telegram.org/bots/api#inlinekeyboardbutton
|
18
|
+
on_message do
|
19
|
+
next_action = if params.callback_data == "create"
|
20
|
+
Actions::SelectDoctor
|
21
|
+
elsif params.callback_data == "list"
|
22
|
+
Actions::ListAppointments
|
23
|
+
end
|
24
|
+
|
25
|
+
redirect_to next_action
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Actions::ListAppointments < TelegramWorkflow::Action
|
2
|
+
def initial
|
3
|
+
on_redirect do
|
4
|
+
appointments = session[:appointments].map do |app|
|
5
|
+
"<b>#{app[:name]}</b> on #{app[:date].to_s}"
|
6
|
+
end
|
7
|
+
|
8
|
+
# refer to https://core.telegram.org/bots/api#sendmessage for the list of available parameters
|
9
|
+
client.send_message text: appointments.join("\n"), parse_mode: "HTML"
|
10
|
+
redirect_to Actions::ListActions
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Actions::SelectDoctor < TelegramWorkflow::Action
|
2
|
+
def initial
|
3
|
+
on_redirect do
|
4
|
+
available_doctors = [
|
5
|
+
[{ text: "Family Medicine", callback_data: "family" }],
|
6
|
+
[{ text: "Emergency Medicine", callback_data: "emergency" }],
|
7
|
+
[{ text: "Pediatrics", callback_data: "pediatrics" }]
|
8
|
+
]
|
9
|
+
|
10
|
+
client.send_actions "Select a doctor:", available_doctors
|
11
|
+
end
|
12
|
+
|
13
|
+
on_message do
|
14
|
+
# pass `specialty` parameter to the next action
|
15
|
+
# https://github.com/rsamoilov/telegram_workflow#redirect_toaction_or_class-flash_params--
|
16
|
+
redirect_to Actions::CreateAppointment, specialty: params.callback_data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/example/client.rb
ADDED
data/example/flow.jpg
ADDED
Binary file
|
data/example/main.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "environment"
|
2
|
+
|
3
|
+
TelegramWorkflow.configure do |config|
|
4
|
+
config.start_action = Actions::Start
|
5
|
+
config.client = Client
|
6
|
+
config.session_store = TelegramWorkflow::Stores::File.new
|
7
|
+
config.api_token = <YOUR_TOKEN>
|
8
|
+
end
|
9
|
+
|
10
|
+
trap "SIGINT" do
|
11
|
+
puts "Exiting..."
|
12
|
+
TelegramWorkflow.stop_updates
|
13
|
+
end
|
14
|
+
|
15
|
+
TelegramWorkflow.updates(timeout: 5).each do |params|
|
16
|
+
TelegramWorkflow.process(params)
|
17
|
+
end
|
data/lib/telegram_workflow.rb
CHANGED
@@ -33,6 +33,7 @@ require "telegram_workflow/session"
|
|
33
33
|
require "telegram_workflow/version"
|
34
34
|
require "telegram_workflow/updates"
|
35
35
|
require "telegram_workflow/workflow"
|
36
|
+
require "telegram_workflow/input_file"
|
36
37
|
require "telegram_workflow/stores/in_memory"
|
37
38
|
require "telegram_workflow/stores/file"
|
38
39
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class TelegramWorkflow::Client
|
2
|
-
API_VERSION = "4.
|
2
|
+
API_VERSION = "4.9"
|
3
3
|
WebhookFilePath = Pathname.new("tmp/telegram_workflow/webhook_url.txt")
|
4
4
|
|
5
5
|
AVAILABLE_ACTIONS = %i(
|
@@ -129,8 +129,11 @@ class TelegramWorkflow::Client
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def make_request(action, params)
|
132
|
+
has_file_params = params.any? { |_, param| param.is_a?(TelegramWorkflow::InputFile) }
|
133
|
+
request_type = has_file_params ? :form : :json
|
134
|
+
|
132
135
|
response = ::Retryable.retryable(tries: 3, on: HTTP::ConnectionError) do
|
133
|
-
::HTTP.post("#{@api_url}/#{action}",
|
136
|
+
::HTTP.post("#{@api_url}/#{action}", request_type => { chat_id: @chat_id, **params })
|
134
137
|
end
|
135
138
|
|
136
139
|
if response.code != 200
|
@@ -11,6 +11,12 @@ module TelegramWorkflow::Errors
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
class StartRedirect < StandardError
|
15
|
+
def initialize(msg = "You cannot redirect to a start action.")
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
14
20
|
class NoSession < StandardError
|
15
21
|
def initialize(msg = "Session could not be fetched for this update.")
|
16
22
|
super
|
@@ -36,6 +36,7 @@ class TelegramWorkflow::Workflow
|
|
36
36
|
def redirect_to(action_or_step, session_params = nil)
|
37
37
|
raise TelegramWorkflow::Errors::DoubleRedirect if @redirect_to
|
38
38
|
raise TelegramWorkflow::Errors::SharedRedirect if action_or_step == :shared
|
39
|
+
raise TelegramWorkflow::Errors::StartRedirect if action_or_step == TelegramWorkflow.config.start_action
|
39
40
|
|
40
41
|
@redirect_to = action_or_step
|
41
42
|
@session_params = session_params
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: telegram_workflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Samoilov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -82,11 +82,23 @@ files:
|
|
82
82
|
- Rakefile
|
83
83
|
- bin/console
|
84
84
|
- bin/setup
|
85
|
+
- example/Gemfile
|
86
|
+
- example/README.md
|
87
|
+
- example/actions/create_appointment.rb
|
88
|
+
- example/actions/list_actions.rb
|
89
|
+
- example/actions/list_appointments.rb
|
90
|
+
- example/actions/select_doctor.rb
|
91
|
+
- example/actions/start.rb
|
92
|
+
- example/client.rb
|
93
|
+
- example/environment.rb
|
94
|
+
- example/flow.jpg
|
95
|
+
- example/main.rb
|
85
96
|
- lib/telegram_workflow.rb
|
86
97
|
- lib/telegram_workflow/action.rb
|
87
98
|
- lib/telegram_workflow/client.rb
|
88
99
|
- lib/telegram_workflow/config.rb
|
89
100
|
- lib/telegram_workflow/errors.rb
|
101
|
+
- lib/telegram_workflow/input_file.rb
|
90
102
|
- lib/telegram_workflow/params.rb
|
91
103
|
- lib/telegram_workflow/rspec.rb
|
92
104
|
- lib/telegram_workflow/session.rb
|