telegram_workflow 1.0.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +360 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/telegram_workflow.rb +38 -0
- data/lib/telegram_workflow/action.rb +44 -0
- data/lib/telegram_workflow/client.rb +140 -0
- data/lib/telegram_workflow/config.rb +48 -0
- data/lib/telegram_workflow/errors.rb +23 -0
- data/lib/telegram_workflow/params.rb +61 -0
- data/lib/telegram_workflow/rspec.rb +80 -0
- data/lib/telegram_workflow/session.rb +46 -0
- data/lib/telegram_workflow/stores/file.rb +18 -0
- data/lib/telegram_workflow/stores/in_memory.rb +13 -0
- data/lib/telegram_workflow/updates.rb +18 -0
- data/lib/telegram_workflow/version.rb +3 -0
- data/lib/telegram_workflow/workflow.rb +95 -0
- data/telegram_workflow.gemspec +28 -0
- metadata +111 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "telegram_workflow"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "http"
|
2
|
+
|
3
|
+
module TelegramWorkflow
|
4
|
+
module Stores
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.process(params)
|
8
|
+
Workflow.new(params).process
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.updates(offset: nil, limit: nil, timeout: 60, allowed_updates: nil)
|
12
|
+
params = {}
|
13
|
+
params[:offset] = offset if offset
|
14
|
+
params[:limit] = limit if limit
|
15
|
+
params[:timeout] = timeout if timeout
|
16
|
+
params[:allowed_updates] = allowed_updates if allowed_updates
|
17
|
+
|
18
|
+
Updates.new(params).enum
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require "telegram_workflow/action"
|
23
|
+
require "telegram_workflow/client"
|
24
|
+
require "telegram_workflow/config"
|
25
|
+
require "telegram_workflow/errors"
|
26
|
+
require "telegram_workflow/params"
|
27
|
+
require "telegram_workflow/session"
|
28
|
+
require "telegram_workflow/version"
|
29
|
+
require "telegram_workflow/updates"
|
30
|
+
require "telegram_workflow/workflow"
|
31
|
+
require "telegram_workflow/stores/in_memory"
|
32
|
+
require "telegram_workflow/stores/file"
|
33
|
+
|
34
|
+
TelegramWorkflow.__after_configuration do |config|
|
35
|
+
if config.webhook_url
|
36
|
+
TelegramWorkflow::Client.new.__setup_webhook
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class TelegramWorkflow::Action
|
2
|
+
extend ::Forwardable
|
3
|
+
def_delegators :@__workflow, :client, :params, :redirect_to
|
4
|
+
|
5
|
+
def initialize(workflow, session, flash)
|
6
|
+
@__workflow = workflow
|
7
|
+
@__session = session
|
8
|
+
@__flash = flash
|
9
|
+
end
|
10
|
+
|
11
|
+
def shared
|
12
|
+
:__continue
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_redirect(&block)
|
16
|
+
@on_redirect = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_message(&block)
|
20
|
+
@on_message = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def __reset_callbacks
|
24
|
+
@on_redirect = @on_message = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def __run_on_redirect
|
28
|
+
@on_redirect.call if @on_redirect
|
29
|
+
end
|
30
|
+
|
31
|
+
def __run_on_message
|
32
|
+
@on_message.call if @on_message
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def session
|
38
|
+
@__session
|
39
|
+
end
|
40
|
+
|
41
|
+
def flash
|
42
|
+
@__flash
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
class TelegramWorkflow::Client
|
2
|
+
API_VERSION = "4.8"
|
3
|
+
WebhookFilePath = Pathname.new("tmp/telegram_workflow/webhook_url.txt")
|
4
|
+
|
5
|
+
AVAILABLE_ACTIONS = %i(
|
6
|
+
getUpdates
|
7
|
+
getWebhookInfo
|
8
|
+
|
9
|
+
getMe
|
10
|
+
sendMessage
|
11
|
+
forwardMessage
|
12
|
+
sendPhoto
|
13
|
+
sendAudio
|
14
|
+
sendDocument
|
15
|
+
sendVideo
|
16
|
+
sendAnimation
|
17
|
+
sendVoice
|
18
|
+
sendVideoNote
|
19
|
+
sendMediaGroup
|
20
|
+
sendLocation
|
21
|
+
editMessageLiveLocation
|
22
|
+
stopMessageLiveLocation
|
23
|
+
sendVenue
|
24
|
+
sendContact
|
25
|
+
sendPoll
|
26
|
+
sendDice
|
27
|
+
sendChatAction
|
28
|
+
getUserProfilePhotos
|
29
|
+
getFile
|
30
|
+
kickChatMember
|
31
|
+
unbanChatMember
|
32
|
+
restrictChatMember
|
33
|
+
promoteChatMember
|
34
|
+
setChatAdministratorCustomTitle
|
35
|
+
setChatPermissions
|
36
|
+
exportChatInviteLink
|
37
|
+
setChatPhoto
|
38
|
+
deleteChatPhoto
|
39
|
+
setChatTitle
|
40
|
+
setChatDescription
|
41
|
+
pinChatMessage
|
42
|
+
unpinChatMessage
|
43
|
+
leaveChat
|
44
|
+
getChat
|
45
|
+
getChatAdministrators
|
46
|
+
getChatMembersCount
|
47
|
+
getChatMember
|
48
|
+
setChatStickerSet
|
49
|
+
deleteChatStickerSet
|
50
|
+
answerCallbackQuery
|
51
|
+
setMyCommands
|
52
|
+
getMyCommands
|
53
|
+
|
54
|
+
editMessageText
|
55
|
+
editMessageCaption
|
56
|
+
editMessageMedia
|
57
|
+
editMessageReplyMarkup
|
58
|
+
stopPoll
|
59
|
+
deleteMessage
|
60
|
+
|
61
|
+
sendSticker
|
62
|
+
getStickerSet
|
63
|
+
uploadStickerFile
|
64
|
+
createNewStickerSet
|
65
|
+
addStickerToSet
|
66
|
+
setStickerPositionInSet
|
67
|
+
deleteStickerFromSet
|
68
|
+
setStickerSetThumb
|
69
|
+
|
70
|
+
answerInlineQuery
|
71
|
+
|
72
|
+
sendInvoice
|
73
|
+
answerShippingQuery
|
74
|
+
answerPreCheckoutQuery
|
75
|
+
|
76
|
+
setPassportDataErrors
|
77
|
+
|
78
|
+
sendGame
|
79
|
+
setGameScore
|
80
|
+
getGameHighScores
|
81
|
+
)
|
82
|
+
|
83
|
+
AVAILABLE_ACTIONS.each do |action|
|
84
|
+
method_name = action.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
85
|
+
|
86
|
+
define_method(method_name) do |params = {}|
|
87
|
+
make_request(action, params)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(chat_id = nil)
|
92
|
+
@chat_id = chat_id
|
93
|
+
@webhook_url = TelegramWorkflow.config.webhook_url
|
94
|
+
@api_url = "https://api.telegram.org/bot#{TelegramWorkflow.config.api_token}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_webhook(params = {})
|
98
|
+
make_request("setWebhook", params)
|
99
|
+
cached_webhook_url(new_url: @webhook_url)
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete_webhook
|
103
|
+
make_request("deleteWebhook", {})
|
104
|
+
cached_webhook_url(new_url: "")
|
105
|
+
end
|
106
|
+
|
107
|
+
def __setup_webhook
|
108
|
+
TelegramWorkflow.config.logger.info "[TelegramWorkflow] Checking webhook setup..."
|
109
|
+
|
110
|
+
if cached_webhook_url != @webhook_url
|
111
|
+
TelegramWorkflow.config.logger.info "[TelegramWorkflow] Setting up a new webhook..."
|
112
|
+
set_webhook(url: @webhook_url)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def cached_webhook_url(new_url: nil)
|
119
|
+
unless WebhookFilePath.exist?
|
120
|
+
WebhookFilePath.dirname.mkpath
|
121
|
+
WebhookFilePath.write("")
|
122
|
+
end
|
123
|
+
|
124
|
+
if new_url.nil?
|
125
|
+
WebhookFilePath.read
|
126
|
+
else
|
127
|
+
WebhookFilePath.write(new_url)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def make_request(action, params)
|
132
|
+
response = ::HTTP.post("#{@api_url}/#{action}", json: { chat_id: @chat_id, **params })
|
133
|
+
|
134
|
+
if response.code != 200
|
135
|
+
raise TelegramWorkflow::Errors::ApiError, response.parse["description"]
|
136
|
+
end
|
137
|
+
|
138
|
+
response.parse
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
unless defined?(Rails)
|
2
|
+
require "logger"
|
3
|
+
end
|
4
|
+
|
5
|
+
module TelegramWorkflow
|
6
|
+
class << self
|
7
|
+
def config
|
8
|
+
@config ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure
|
12
|
+
yield(config)
|
13
|
+
config.verify!
|
14
|
+
|
15
|
+
@__after_configuration.call(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def __after_configuration(&block)
|
19
|
+
@__after_configuration = block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Configuration
|
24
|
+
attr_accessor :session_store, :logger, :client, :start_action, :webhook_url, :api_token
|
25
|
+
|
26
|
+
REQUIRED_PARAMS = %i(session_store start_action api_token)
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@client = TelegramWorkflow::Client
|
30
|
+
|
31
|
+
if defined?(Rails)
|
32
|
+
@session_store = Rails.cache
|
33
|
+
@logger = Rails.logger
|
34
|
+
else
|
35
|
+
@session_store = TelegramWorkflow::Stores::InMemory.new
|
36
|
+
@logger = Logger.new(STDOUT)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify!
|
41
|
+
blank_params = REQUIRED_PARAMS.select { |p| send(p).nil? }
|
42
|
+
|
43
|
+
if blank_params.any?
|
44
|
+
raise TelegramWorkflow::Errors::MissingConfiguration, blank_params
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module TelegramWorkflow::Errors
|
2
|
+
class DoubleRedirect < StandardError
|
3
|
+
def initialize(msg = "Redirect was called multiple times in the step callback.")
|
4
|
+
super
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class SharedRedirect < StandardError
|
9
|
+
def initialize(msg = "You cannot redirect to a shared step.")
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ApiError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class MissingConfiguration < StandardError
|
18
|
+
def initialize(missing_config_params)
|
19
|
+
msg = "Missing required configuration params: #{missing_config_params.join(", ")}"
|
20
|
+
super(msg)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TelegramWorkflow::Params
|
4
|
+
def initialize(params)
|
5
|
+
@params = params
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](key)
|
9
|
+
@params[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def user
|
13
|
+
@user ||= @params.dig("message", "from") ||
|
14
|
+
@params.dig("callback_query", "from") ||
|
15
|
+
@params.dig("pre_checkout_query", "from") ||
|
16
|
+
@params.dig("shipping_query", "from") ||
|
17
|
+
@params.dig("inline_query", "from") ||
|
18
|
+
@params.dig("chosen_inline_result", "from")
|
19
|
+
end
|
20
|
+
|
21
|
+
def language_code
|
22
|
+
user["language_code"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def user_id
|
26
|
+
user["id"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def username
|
30
|
+
user["username"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def chat_id
|
34
|
+
@params.dig("message", "chat", "id") ||
|
35
|
+
@params.dig("callback_query", "message", "chat", "id") ||
|
36
|
+
@params.dig("edited_message", "chat", "id") ||
|
37
|
+
@params.dig("channel_post", "chat", "id") ||
|
38
|
+
@params.dig("edited_channel_post", "chat", "id")
|
39
|
+
end
|
40
|
+
|
41
|
+
def message_text
|
42
|
+
@params.dig("message", "text")
|
43
|
+
end
|
44
|
+
|
45
|
+
def callback_data
|
46
|
+
@params.dig("callback_query", "data")
|
47
|
+
end
|
48
|
+
|
49
|
+
def start?
|
50
|
+
!!message_text&.start_with?("/start")
|
51
|
+
end
|
52
|
+
|
53
|
+
def command?
|
54
|
+
!!message_text&.start_with?("/")
|
55
|
+
end
|
56
|
+
|
57
|
+
def deep_link_payload
|
58
|
+
match = /\A\/(startgroup|start) (?<payload>.+)\z/.match(message_text)
|
59
|
+
match["payload"] if match
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module TelegramActionExampleGroup
|
2
|
+
def self.included(klass)
|
3
|
+
klass.class_eval do
|
4
|
+
klass.metadata[:type] = :telegram_action
|
5
|
+
|
6
|
+
subject { double(client: spy, flow: spy) }
|
7
|
+
|
8
|
+
let(:current_action) { described_class }
|
9
|
+
let(:action_params) do
|
10
|
+
{
|
11
|
+
"update_id" => 111111111,
|
12
|
+
"message" => {
|
13
|
+
"message_id" => 200,
|
14
|
+
"from" => {
|
15
|
+
"id" => 112233445,
|
16
|
+
},
|
17
|
+
"text" => ""
|
18
|
+
},
|
19
|
+
"callback_query" => {
|
20
|
+
"data" => ""
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
TelegramWorkflow.config.session_store = TelegramWorkflow::Stores::InMemory.new
|
27
|
+
TelegramWorkflow.config.start_action = TestStartAction
|
28
|
+
send_message message_text: "/start"
|
29
|
+
end
|
30
|
+
|
31
|
+
include InstanceMethods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
def send_message(message_text: "", callback_data: "")
|
37
|
+
action_params["message"]["text"] = message_text
|
38
|
+
action_params["callback_query"]["data"] = callback_data
|
39
|
+
|
40
|
+
workflow = TestFlow.new(action_params)
|
41
|
+
workflow.example_group = self
|
42
|
+
|
43
|
+
workflow.process
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class TestFlow < TelegramWorkflow::Workflow
|
48
|
+
attr_accessor :example_group
|
49
|
+
|
50
|
+
def client
|
51
|
+
example_group.subject.client
|
52
|
+
end
|
53
|
+
|
54
|
+
def redirect_to(action_or_step, session_params = nil)
|
55
|
+
super
|
56
|
+
|
57
|
+
if session_params
|
58
|
+
example_group.subject.flow.send(:redirect_to, action_or_step, session_params)
|
59
|
+
else
|
60
|
+
example_group.subject.flow.send(:redirect_to, action_or_step)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class TelegramWorkflow::Action
|
66
|
+
def_delegators :@__workflow, :example_group
|
67
|
+
end
|
68
|
+
|
69
|
+
class TestStartAction < TelegramWorkflow::Action
|
70
|
+
def initial
|
71
|
+
on_message { redirect_to example_group.current_action }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
RSpec.configure do |config|
|
77
|
+
config.include TelegramActionExampleGroup,
|
78
|
+
type: :telegram_action,
|
79
|
+
file_path: %r(spec/telegram_actions)
|
80
|
+
end
|