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
         |