telegram_workflow 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c96cb2e0a64ca84710249746fba7e205101fa03d3708f154e7b9532133c550f6
4
- data.tar.gz: edf5c725fb5100eea709c375657026cc80d3e16075de537422f55604dea150b7
3
+ metadata.gz: 9f33398c3b33397236392a5b645e39b58827df8e453727fd06496ce0b4479e11
4
+ data.tar.gz: 9155e202748c8242adc4f4789623b8c42156658616e545d1aafd2aa4f2f776f7
5
5
  SHA512:
6
- metadata.gz: fb13268c4f33b5add25b82ccc628199aaec2571e57e8731f88b417c5727bb9c9d7639d860a7f6ef9af09110ba12a39db5c4d2ebbd777ea40e2f54c39d0d893ae
7
- data.tar.gz: 5cdeb13d1098f17784dfdca787e2f73a0c8d8ee6cd704d42c5894b3de4b714b2cf1118ffbcd2f3391aa0e977b8a1c7806b3b3f606cf51e85e3df6074b6a61204
6
+ metadata.gz: 4f85e7ab9b297deab054185d03863c8183bf7fbd6a7f1325ec612fa0042ee0999a700e80d91cb5cbed3b945605595904ec1716192b341d39f2402df805ea69f1
7
+ data.tar.gz: 67bf8b6d8abc59f26c2e302f989aaa17777303bc3efdfb29844686d35f62994a2257557e7838188292e2fbbecc7eb7d1ad872c3f123eb9eb1b61b686b046a5ba
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  Gemfile.lock
10
+ *.db
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
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.
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gem "telegram_workflow"
@@ -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
@@ -0,0 +1,9 @@
1
+ class Actions::Start < TelegramWorkflow::Action
2
+ def initial
3
+ # we don't need to store current user id in session or make any other setup,
4
+ # so just redirect to the `ListActions` action
5
+ on_message do
6
+ redirect_to Actions::ListActions
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Client < TelegramWorkflow::Client
2
+ def send_actions(message, actions)
3
+ send_message text: message, reply_markup: { inline_keyboard: actions }
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ require "telegram_workflow"
2
+ require "date"
3
+
4
+ module Actions
5
+ end
6
+
7
+ dependencies = %w(
8
+ ./actions/*.rb
9
+ ./client.rb
10
+ )
11
+ dependencies.each do |path|
12
+ Dir[path].each { |path| require_relative path }
13
+ end
Binary file
@@ -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
@@ -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.8"
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}", json: { chat_id: @chat_id, **params })
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
@@ -0,0 +1,2 @@
1
+ class TelegramWorkflow::InputFile < HTTP::FormData::File
2
+ end
@@ -1,3 +1,3 @@
1
1
  module TelegramWorkflow
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -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.2.0
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-21 00:00:00.000000000 Z
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