chat_sdk-telegram 0.2.1
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/lib/chat_sdk/telegram/adapter.rb +166 -0
- data/lib/chat_sdk/telegram/api_client.rb +111 -0
- data/lib/chat_sdk/telegram/event_parser.rb +127 -0
- data/lib/chat_sdk/telegram/keyboard_renderer.rb +130 -0
- data/lib/chat_sdk/telegram.rb +25 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9c91f00043b554f53ef31b9c1c73540d7495ed60313802c7f4db3f8e41f6729e
|
|
4
|
+
data.tar.gz: 9f081cdebba34e0ecfad397fc72d031770b8ce92ae2cdd0c6c694a3216e7af31
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7e9b5f9eab5c771f56fab8e708d26ffe14a56415add30a4aab5a9b04a61ffc5f64284358c8e8ccf913bb28525e1ccf183352159eb7111572912737c8f0dadf9a
|
|
7
|
+
data.tar.gz: 5da8589bb74c1279e6adc239e385aab47fe4e36536b7cd2030e00b443891a9af799f6b7fe883f10459a72a5f7a4df316acd262279e50dcb72a5b054b7d796037
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack/utils"
|
|
4
|
+
|
|
5
|
+
module ChatSDK
|
|
6
|
+
module Telegram
|
|
7
|
+
class Adapter < ChatSDK::Adapter::Base
|
|
8
|
+
capabilities :edit_messages, :delete_messages, :reactions, :file_uploads,
|
|
9
|
+
:typing_indicator, :streaming_edit, :direct_messages
|
|
10
|
+
|
|
11
|
+
attr_reader :client
|
|
12
|
+
|
|
13
|
+
def initialize(bot_token: nil, secret_token: nil, bot_username: nil)
|
|
14
|
+
@bot_token = bot_token || ENV["TELEGRAM_BOT_TOKEN"]
|
|
15
|
+
@secret_token = secret_token || ENV["TELEGRAM_WEBHOOK_SECRET_TOKEN"]
|
|
16
|
+
@bot_username = bot_username || ENV["TELEGRAM_BOT_USERNAME"]
|
|
17
|
+
|
|
18
|
+
raise ChatSDK::ConfigurationError, "Telegram bot_token required" unless @bot_token
|
|
19
|
+
|
|
20
|
+
@client = ApiClient.new(@bot_token)
|
|
21
|
+
@renderer = KeyboardRenderer.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def name
|
|
25
|
+
:telegram
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Inbound
|
|
29
|
+
def verify_request!(rack_request)
|
|
30
|
+
return true unless @secret_token
|
|
31
|
+
|
|
32
|
+
header_token = rack_request.get_header("HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN")
|
|
33
|
+
|
|
34
|
+
unless header_token
|
|
35
|
+
raise ChatSDK::SignatureVerificationError, "Missing Telegram secret token header"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless Rack::Utils.secure_compare(header_token, @secret_token)
|
|
39
|
+
raise ChatSDK::SignatureVerificationError, "Invalid Telegram secret token"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ack_response(_rack_request)
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_events(rack_request)
|
|
50
|
+
body = rack_request.body.read
|
|
51
|
+
rack_request.body.rewind
|
|
52
|
+
|
|
53
|
+
payload = JSON.parse(body)
|
|
54
|
+
EventParser.parse(payload, bot_username: @bot_username)
|
|
55
|
+
rescue JSON::ParserError
|
|
56
|
+
[]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Outbound
|
|
60
|
+
def post_message(channel_id:, message:, thread_id: nil)
|
|
61
|
+
text, reply_markup = prepare_message_payload(message)
|
|
62
|
+
|
|
63
|
+
result = @client.send_message(
|
|
64
|
+
chat_id: channel_id,
|
|
65
|
+
text: text,
|
|
66
|
+
reply_markup: reply_markup,
|
|
67
|
+
reply_to_message_id: thread_id
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
parse_telegram_message(result, channel_id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def edit_message(channel_id:, message_id:, message:)
|
|
74
|
+
text, reply_markup = prepare_message_payload(message)
|
|
75
|
+
|
|
76
|
+
@client.edit_message_text(
|
|
77
|
+
chat_id: channel_id,
|
|
78
|
+
message_id: message_id,
|
|
79
|
+
text: text,
|
|
80
|
+
reply_markup: reply_markup
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete_message(channel_id:, message_id:)
|
|
85
|
+
@client.delete_message(chat_id: channel_id, message_id: message_id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil)
|
|
89
|
+
@client.send_document(
|
|
90
|
+
chat_id: channel_id,
|
|
91
|
+
document: io,
|
|
92
|
+
filename: filename,
|
|
93
|
+
caption: comment,
|
|
94
|
+
reply_to_message_id: thread_id
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_reaction(channel_id:, message_id:, emoji:)
|
|
99
|
+
@client.set_message_reaction(
|
|
100
|
+
chat_id: channel_id,
|
|
101
|
+
message_id: message_id,
|
|
102
|
+
reaction: [{"type" => "emoji", "emoji" => emoji}]
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def remove_reaction(channel_id:, message_id:, emoji:) # rubocop:disable Lint/UnusedMethodArgument
|
|
107
|
+
@client.set_message_reaction(
|
|
108
|
+
chat_id: channel_id,
|
|
109
|
+
message_id: message_id,
|
|
110
|
+
reaction: []
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def open_dm(user_id)
|
|
115
|
+
user_id
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def start_typing(channel_id:, thread_id: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
119
|
+
@client.send_chat_action(chat_id: channel_id, action: "typing")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def mention(user_id)
|
|
123
|
+
"[user](tg://user?id=#{user_id})"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def render(postable_message)
|
|
127
|
+
if postable_message.card?
|
|
128
|
+
@renderer.render(postable_message.card)
|
|
129
|
+
else
|
|
130
|
+
postable_message.text
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def prepare_message_payload(message)
|
|
137
|
+
msg = ChatSDK::PostableMessage.from(message)
|
|
138
|
+
text = msg.text || msg.card&.fallback_text || ""
|
|
139
|
+
reply_markup = nil
|
|
140
|
+
if msg.card?
|
|
141
|
+
rendered = @renderer.render(msg.card)
|
|
142
|
+
text = rendered[:text] if rendered[:text] && !rendered[:text].empty?
|
|
143
|
+
reply_markup = rendered[:reply_markup]
|
|
144
|
+
end
|
|
145
|
+
[text, reply_markup]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def parse_telegram_message(data, channel_id)
|
|
149
|
+
ChatSDK::Message.new(
|
|
150
|
+
id: data["message_id"]&.to_s,
|
|
151
|
+
text: data["text"] || "",
|
|
152
|
+
author: ChatSDK::Author.new(
|
|
153
|
+
id: data.dig("from", "id")&.to_s || "bot",
|
|
154
|
+
name: data.dig("from", "username") || "bot",
|
|
155
|
+
platform: :telegram,
|
|
156
|
+
bot: true
|
|
157
|
+
),
|
|
158
|
+
thread_id: data["message_id"]&.to_s,
|
|
159
|
+
channel_id: channel_id,
|
|
160
|
+
platform: :telegram,
|
|
161
|
+
raw: data
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ChatSDK
|
|
4
|
+
module Telegram
|
|
5
|
+
class ApiClient
|
|
6
|
+
BASE_URL = "https://api.telegram.org"
|
|
7
|
+
|
|
8
|
+
def initialize(bot_token)
|
|
9
|
+
@bot_token = bot_token
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def send_message(chat_id:, text:, reply_markup: nil, reply_to_message_id: nil)
|
|
13
|
+
body = {"chat_id" => chat_id, "text" => text, "parse_mode" => "Markdown"}
|
|
14
|
+
body["reply_markup"] = reply_markup if reply_markup
|
|
15
|
+
body["reply_to_message_id"] = reply_to_message_id if reply_to_message_id
|
|
16
|
+
request(:post, "sendMessage", body)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def edit_message_text(chat_id:, message_id:, text:, reply_markup: nil)
|
|
20
|
+
body = {"chat_id" => chat_id, "message_id" => message_id, "text" => text, "parse_mode" => "Markdown"}
|
|
21
|
+
body["reply_markup"] = reply_markup if reply_markup
|
|
22
|
+
request(:post, "editMessageText", body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete_message(chat_id:, message_id:)
|
|
26
|
+
request(:post, "deleteMessage", {"chat_id" => chat_id, "message_id" => message_id})
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def send_document(chat_id:, document:, filename:, caption: nil, reply_to_message_id: nil)
|
|
30
|
+
payload = {
|
|
31
|
+
"chat_id" => chat_id,
|
|
32
|
+
"document" => Faraday::Multipart::FilePart.new(document, "application/octet-stream", filename)
|
|
33
|
+
}
|
|
34
|
+
payload["caption"] = caption if caption
|
|
35
|
+
payload["reply_to_message_id"] = reply_to_message_id if reply_to_message_id
|
|
36
|
+
|
|
37
|
+
response = upload_connection.post(api_path("sendDocument"), payload)
|
|
38
|
+
handle_response(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set_message_reaction(chat_id:, message_id:, reaction:)
|
|
42
|
+
request(:post, "setMessageReaction", {
|
|
43
|
+
"chat_id" => chat_id,
|
|
44
|
+
"message_id" => message_id,
|
|
45
|
+
"reaction" => reaction
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def send_chat_action(chat_id:, action:)
|
|
50
|
+
request(:post, "sendChatAction", {"chat_id" => chat_id, "action" => action})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def api_path(method)
|
|
56
|
+
"/bot#{@bot_token}/#{method}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def connection
|
|
60
|
+
@connection ||= Faraday.new(url: BASE_URL) do |f|
|
|
61
|
+
f.request :json
|
|
62
|
+
f.response :json
|
|
63
|
+
f.adapter :net_http
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def upload_connection
|
|
68
|
+
@upload_connection ||= Faraday.new(url: BASE_URL) do |f|
|
|
69
|
+
f.request :multipart
|
|
70
|
+
f.response :json
|
|
71
|
+
f.adapter :net_http
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def request(method, api_method, body = nil)
|
|
76
|
+
response = connection.public_send(method, api_path(api_method)) do |req|
|
|
77
|
+
req.body = body if body && method != :get
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
handle_response(response)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def handle_response(response)
|
|
84
|
+
body = response.body
|
|
85
|
+
|
|
86
|
+
if response.success? && body.is_a?(Hash) && body["ok"]
|
|
87
|
+
return body["result"]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if response.status == 429
|
|
91
|
+
retry_after = body.is_a?(Hash) ? body.dig("parameters", "retry_after")&.to_i : nil
|
|
92
|
+
raise ChatSDK::RateLimitedError.new(
|
|
93
|
+
"Telegram API rate limited",
|
|
94
|
+
retry_after: retry_after,
|
|
95
|
+
status: response.status,
|
|
96
|
+
body: body,
|
|
97
|
+
adapter_name: :telegram
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
description = body.is_a?(Hash) ? body["description"] : response.status
|
|
102
|
+
raise ChatSDK::PlatformError.new(
|
|
103
|
+
"Telegram API error: #{description}",
|
|
104
|
+
status: response.status,
|
|
105
|
+
body: body,
|
|
106
|
+
adapter_name: :telegram
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ChatSDK
|
|
4
|
+
module Telegram
|
|
5
|
+
class EventParser
|
|
6
|
+
class << self
|
|
7
|
+
def parse(payload, bot_username: nil)
|
|
8
|
+
return [] unless payload.is_a?(Hash)
|
|
9
|
+
|
|
10
|
+
if payload["callback_query"]
|
|
11
|
+
parse_callback_query(payload["callback_query"])
|
|
12
|
+
elsif payload["message"]
|
|
13
|
+
parse_message(payload["message"], bot_username: bot_username)
|
|
14
|
+
else
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def parse_message(message, bot_username: nil)
|
|
22
|
+
return parse_bot_command(message, extract_user(message), message.dig("chat", "id")&.to_s, resolve_thread_id(message)) if bot_command?(message)
|
|
23
|
+
|
|
24
|
+
user = extract_user(message)
|
|
25
|
+
chat_id = message.dig("chat", "id")&.to_s
|
|
26
|
+
chat_type = message.dig("chat", "type")
|
|
27
|
+
text = message["text"] || ""
|
|
28
|
+
thread_id = resolve_thread_id(message)
|
|
29
|
+
|
|
30
|
+
msg = ChatSDK::Message.new(
|
|
31
|
+
id: message["message_id"]&.to_s,
|
|
32
|
+
text: text,
|
|
33
|
+
author: ChatSDK::Author.new(id: user[:id], name: user[:name], platform: :telegram, bot: false),
|
|
34
|
+
thread_id: thread_id,
|
|
35
|
+
channel_id: chat_id,
|
|
36
|
+
platform: :telegram,
|
|
37
|
+
raw: message
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
event_class = if mention?(text, bot_username)
|
|
41
|
+
ChatSDK::Events::Mention
|
|
42
|
+
elsif chat_type == "private"
|
|
43
|
+
ChatSDK::Events::DirectMessage
|
|
44
|
+
else
|
|
45
|
+
ChatSDK::Events::SubscribedMessage
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
[event_class.new(message: msg, thread_id: thread_id, channel_id: chat_id,
|
|
49
|
+
platform: :telegram, adapter_name: :telegram, raw: message)]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parse_callback_query(callback)
|
|
53
|
+
user_info = callback["from"] || {}
|
|
54
|
+
message = callback["message"] || {}
|
|
55
|
+
chat_id = message.dig("chat", "id")&.to_s
|
|
56
|
+
message_id = message["message_id"]&.to_s
|
|
57
|
+
callback_data = callback["data"] || ""
|
|
58
|
+
|
|
59
|
+
action_id, value = callback_data.split(":", 2)
|
|
60
|
+
value ||= action_id
|
|
61
|
+
|
|
62
|
+
user = ChatSDK::Author.new(
|
|
63
|
+
id: user_info["id"]&.to_s || "unknown",
|
|
64
|
+
name: user_info["username"] || user_info["first_name"] || "unknown",
|
|
65
|
+
platform: :telegram,
|
|
66
|
+
bot: false
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
[ChatSDK::Events::Action.new(
|
|
70
|
+
action_id: action_id,
|
|
71
|
+
value: value,
|
|
72
|
+
user: user,
|
|
73
|
+
thread_id: message_id,
|
|
74
|
+
channel_id: chat_id,
|
|
75
|
+
platform: :telegram,
|
|
76
|
+
adapter_name: :telegram,
|
|
77
|
+
raw: callback
|
|
78
|
+
)]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_user(message)
|
|
82
|
+
from = message["from"] || {}
|
|
83
|
+
{
|
|
84
|
+
id: from["id"]&.to_s || "unknown",
|
|
85
|
+
name: from["username"] || from["first_name"] || "unknown"
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def resolve_thread_id(message)
|
|
90
|
+
reply = message.dig("reply_to_message", "message_id")
|
|
91
|
+
(reply || message["message_id"])&.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def bot_command?(message)
|
|
95
|
+
entities = message["entities"]
|
|
96
|
+
return false unless entities.is_a?(Array)
|
|
97
|
+
|
|
98
|
+
entities.any? { |e| e["type"] == "bot_command" }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def mention?(text, bot_username)
|
|
102
|
+
return false unless bot_username
|
|
103
|
+
|
|
104
|
+
text.include?("@#{bot_username}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_bot_command(message, user, chat_id, thread_id)
|
|
108
|
+
text = message["text"] || ""
|
|
109
|
+
parts = text.split(" ", 2)
|
|
110
|
+
command = parts[0]&.split("@")&.first || ""
|
|
111
|
+
args = parts[1] || ""
|
|
112
|
+
|
|
113
|
+
[ChatSDK::Events::SlashCommand.new(
|
|
114
|
+
command: command,
|
|
115
|
+
text: args,
|
|
116
|
+
user_id: user[:id],
|
|
117
|
+
channel_id: chat_id,
|
|
118
|
+
trigger_id: message["message_id"]&.to_s,
|
|
119
|
+
platform: :telegram,
|
|
120
|
+
adapter_name: :telegram,
|
|
121
|
+
raw: message
|
|
122
|
+
)]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ChatSDK
|
|
4
|
+
module Telegram
|
|
5
|
+
class KeyboardRenderer
|
|
6
|
+
def render(node)
|
|
7
|
+
case node.type
|
|
8
|
+
when :card then render_card(node)
|
|
9
|
+
else render_single(node)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def render_card(node)
|
|
16
|
+
text_parts = []
|
|
17
|
+
keyboard_rows = []
|
|
18
|
+
|
|
19
|
+
text_parts << "*#{node.attributes[:title]}*" if node.attributes[:title]
|
|
20
|
+
text_parts << "_#{node.attributes[:subtitle]}_" if node.attributes[:subtitle]
|
|
21
|
+
|
|
22
|
+
node.children.each do |child|
|
|
23
|
+
case child.type
|
|
24
|
+
when :text
|
|
25
|
+
text_parts << child.attributes[:content]
|
|
26
|
+
when :divider
|
|
27
|
+
text_parts << "───"
|
|
28
|
+
when :image
|
|
29
|
+
text_parts << "[Image](#{child.attributes[:url]})"
|
|
30
|
+
when :fields
|
|
31
|
+
child.children.each do |field|
|
|
32
|
+
text_parts << "*#{field.attributes[:label]}*: #{field.attributes[:value]}"
|
|
33
|
+
end
|
|
34
|
+
when :section
|
|
35
|
+
render_section_into(child, text_parts, keyboard_rows)
|
|
36
|
+
when :actions
|
|
37
|
+
render_actions_into(child, keyboard_rows)
|
|
38
|
+
when :button
|
|
39
|
+
keyboard_rows << [render_button(child)]
|
|
40
|
+
when :link_button
|
|
41
|
+
keyboard_rows << [render_link_button(child)]
|
|
42
|
+
when :select
|
|
43
|
+
render_select_into(child, keyboard_rows)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
result = {text: text_parts.join("\n")}
|
|
48
|
+
unless keyboard_rows.empty?
|
|
49
|
+
result[:reply_markup] = {"inline_keyboard" => keyboard_rows}
|
|
50
|
+
end
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def render_section_into(node, text_parts, keyboard_rows)
|
|
55
|
+
text_parts << "*#{node.attributes[:title]}*" if node.attributes[:title]
|
|
56
|
+
|
|
57
|
+
node.children.each do |child|
|
|
58
|
+
case child.type
|
|
59
|
+
when :text
|
|
60
|
+
text_parts << child.attributes[:content]
|
|
61
|
+
when :fields
|
|
62
|
+
child.children.each do |field|
|
|
63
|
+
text_parts << "*#{field.attributes[:label]}*: #{field.attributes[:value]}"
|
|
64
|
+
end
|
|
65
|
+
when :actions
|
|
66
|
+
render_actions_into(child, keyboard_rows)
|
|
67
|
+
when :button
|
|
68
|
+
keyboard_rows << [render_button(child)]
|
|
69
|
+
when :link_button
|
|
70
|
+
keyboard_rows << [render_link_button(child)]
|
|
71
|
+
when :select
|
|
72
|
+
render_select_into(child, keyboard_rows)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def render_actions_into(node, keyboard_rows)
|
|
78
|
+
row = []
|
|
79
|
+
node.children.each do |child|
|
|
80
|
+
if child.type == :select
|
|
81
|
+
keyboard_rows << row unless row.empty?
|
|
82
|
+
row = []
|
|
83
|
+
render_select_into(child, keyboard_rows)
|
|
84
|
+
else
|
|
85
|
+
comp = render_component(child)
|
|
86
|
+
row << comp if comp
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
keyboard_rows << row unless row.empty?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def render_component(node)
|
|
93
|
+
case node.type
|
|
94
|
+
when :button then render_button(node)
|
|
95
|
+
when :link_button then render_link_button(node)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def render_button(node)
|
|
100
|
+
callback_data = if node.attributes[:value]
|
|
101
|
+
"#{node.attributes[:id]}:#{node.attributes[:value]}"
|
|
102
|
+
else
|
|
103
|
+
node.attributes[:id] || "button"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
{"text" => node.attributes[:text], "callback_data" => callback_data}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def render_link_button(node)
|
|
110
|
+
{"text" => node.attributes[:text], "url" => node.attributes[:url]}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def render_select_into(node, keyboard_rows)
|
|
114
|
+
node.children.each do |opt|
|
|
115
|
+
callback_data = "#{node.attributes[:id]}:#{opt.attributes[:value]}"
|
|
116
|
+
keyboard_rows << [{"text" => opt.attributes[:text], "callback_data" => callback_data}]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def render_single(node)
|
|
121
|
+
case node.type
|
|
122
|
+
when :text
|
|
123
|
+
{text: node.attributes[:content]}
|
|
124
|
+
else
|
|
125
|
+
{text: node.fallback_text}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "chat_sdk"
|
|
4
|
+
require "faraday"
|
|
5
|
+
require "faraday/net_http"
|
|
6
|
+
require "zeitwerk"
|
|
7
|
+
|
|
8
|
+
module ChatSDK
|
|
9
|
+
module Telegram
|
|
10
|
+
class << self
|
|
11
|
+
def loader
|
|
12
|
+
@loader ||= begin
|
|
13
|
+
loader = Zeitwerk::Loader.new
|
|
14
|
+
loader.tag = "chat_sdk-telegram"
|
|
15
|
+
loader.inflector.inflect("chat_sdk" => "ChatSDK")
|
|
16
|
+
loader.push_dir("#{__dir__}/telegram", namespace: ChatSDK::Telegram)
|
|
17
|
+
loader.setup
|
|
18
|
+
loader
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ChatSDK::Telegram.loader
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: chat_sdk-telegram
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Rootly
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: chat_sdk
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
description: Telegram Bot API adapter for the ChatSDK framework
|
|
41
|
+
email:
|
|
42
|
+
- eng@rootly.com
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- lib/chat_sdk/telegram.rb
|
|
48
|
+
- lib/chat_sdk/telegram/adapter.rb
|
|
49
|
+
- lib/chat_sdk/telegram/api_client.rb
|
|
50
|
+
- lib/chat_sdk/telegram/event_parser.rb
|
|
51
|
+
- lib/chat_sdk/telegram/keyboard_renderer.rb
|
|
52
|
+
homepage: https://github.com/rootlyhq/chat-sdk
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
rubygems_mfa_required: 'true'
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: 3.2.0
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 4.0.16
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: Telegram adapter for ChatSDK
|
|
74
|
+
test_files: []
|