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 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