gruubY 0.2.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/LICENSE +21 -0
- data/README.md +6 -0
- data/Rakefile +51 -0
- data/config/config.rb +5 -0
- data/example/bot.rb +160 -0
- data/example/generate_user_session.rb +81 -0
- data/example/ntgcalls.rb +80 -0
- data/example/tdlib.rb +77 -0
- data/example/userbot.rb +133 -0
- data/lib/grubY/api.rb +363 -0
- data/lib/grubY/async.rb +13 -0
- data/lib/grubY/bot.rb +90 -0
- data/lib/grubY/bound.rb +14 -0
- data/lib/grubY/client.rb +296 -0
- data/lib/grubY/context.rb +242 -0
- data/lib/grubY/dispatcher.rb +60 -0
- data/lib/grubY/enums.rb +62 -0
- data/lib/grubY/exception.rb +5 -0
- data/lib/grubY/file_stream.rb +17 -0
- data/lib/grubY/filters.rb +292 -0
- data/lib/grubY/group_manager.rb +213 -0
- data/lib/grubY/handlers.rb +170 -0
- data/lib/grubY/keyboard.rb +49 -0
- data/lib/grubY/media.rb +38 -0
- data/lib/grubY/middleware.rb +18 -0
- data/lib/grubY/ntgcalls/native.rb +100 -0
- data/lib/grubY/ntgcalls.rb +478 -0
- data/lib/grubY/plugin.rb +14 -0
- data/lib/grubY/raw.rb +25 -0
- data/lib/grubY/raw_types.rb +57 -0
- data/lib/grubY/retry.rb +14 -0
- data/lib/grubY/server.rb +18 -0
- data/lib/grubY/session.rb +30 -0
- data/lib/grubY/tdlib/client.rb +565 -0
- data/lib/grubY/tdlib/client_manager.rb +37 -0
- data/lib/grubY/tdlib/decorators.rb +17 -0
- data/lib/grubY/tdlib/errors.rb +6 -0
- data/lib/grubY/tdlib/group_manager.rb +58 -0
- data/lib/grubY/tdlib/native.rb +72 -0
- data/lib/grubY/tdlib/schema_builder.rb +237 -0
- data/lib/grubY/tdlib/tdjson.rb +49 -0
- data/lib/grubY/tdlib/user_session.rb +69 -0
- data/lib/grubY/types/base_object.rb +72 -0
- data/lib/grubY/types/bound_entities.rb +245 -0
- data/lib/grubY/types/chat.rb +93 -0
- data/lib/grubY/types/chat_info.rb +31 -0
- data/lib/grubY/types/extra.rb +251 -0
- data/lib/grubY/types/message.rb +295 -0
- data/lib/grubY/types/message_entity.rb +14 -0
- data/lib/grubY/types/registry.rb +67 -0
- data/lib/grubY/types/user.rb +31 -0
- data/lib/grubY/webapp.rb +22 -0
- data/lib/grubY.rb +41 -0
- data/plugins/logger.rb +7 -0
- data/plugins/media_tools.rb +13 -0
- metadata +110 -0
data/lib/grubY/client.rb
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
require_relative "bot"
|
|
2
|
+
require_relative "group_manager"
|
|
3
|
+
require_relative "handlers"
|
|
4
|
+
require_relative "filters"
|
|
5
|
+
require_relative "types/message"
|
|
6
|
+
require_relative "types/chat"
|
|
7
|
+
require_relative "types/user"
|
|
8
|
+
|
|
9
|
+
module GrubY
|
|
10
|
+
class Client
|
|
11
|
+
attr_reader :bot, :api, :session, :group_manager
|
|
12
|
+
|
|
13
|
+
def initialize(token, session: Session.new, polling_timeout: 30)
|
|
14
|
+
@bot = Bot.new(token, session: session)
|
|
15
|
+
@api = @bot.api
|
|
16
|
+
@session = session
|
|
17
|
+
@group_manager = GroupManager.new(@api, session: @session)
|
|
18
|
+
@polling_timeout = polling_timeout
|
|
19
|
+
@polling_thread = nil
|
|
20
|
+
@running = false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start
|
|
24
|
+
return if @running
|
|
25
|
+
|
|
26
|
+
@bot.emit(:connect)
|
|
27
|
+
@running = true
|
|
28
|
+
start_polling
|
|
29
|
+
@bot.emit(:start)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stop
|
|
34
|
+
@bot.emit(:stop) if @running
|
|
35
|
+
@running = false
|
|
36
|
+
@polling_thread&.kill
|
|
37
|
+
@polling_thread = nil
|
|
38
|
+
@bot.emit(:disconnect)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def restart
|
|
43
|
+
stop
|
|
44
|
+
start
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def connect
|
|
48
|
+
start
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def disconnect
|
|
52
|
+
stop
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run
|
|
56
|
+
start
|
|
57
|
+
idle
|
|
58
|
+
stop
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def terminate
|
|
62
|
+
stop
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def add_handler(handler_or_event, filter=nil, group: 0, &block)
|
|
66
|
+
if handler_or_event.is_a?(GrubY::Handlers::BaseHandler)
|
|
67
|
+
register_handler_object(handler_or_event)
|
|
68
|
+
else
|
|
69
|
+
@bot.on(handler_or_event, filter, group: group, &block)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def remove_handler(event, handler_id = nil, &block)
|
|
74
|
+
@bot.remove_handler(event, handler_id, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def on(event, filter=nil, &block)
|
|
78
|
+
add_handler(event, filter, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
on_message: GrubY::Handlers::MessageHandler,
|
|
83
|
+
on_edited_message: GrubY::Handlers::EditedMessageHandler,
|
|
84
|
+
on_business_message: GrubY::Handlers::BusinessMessageHandler,
|
|
85
|
+
on_edited_business_message: GrubY::Handlers::EditedBusinessMessageHandler,
|
|
86
|
+
on_deleted_messages: GrubY::Handlers::DeletedMessagesHandler,
|
|
87
|
+
on_deleted_business_messages: GrubY::Handlers::DeletedBusinessMessagesHandler,
|
|
88
|
+
on_message_reaction_count: GrubY::Handlers::MessageReactionCountHandler,
|
|
89
|
+
on_message_reaction: GrubY::Handlers::MessageReactionHandler,
|
|
90
|
+
on_business_connection: GrubY::Handlers::BusinessConnectionHandler,
|
|
91
|
+
on_story: GrubY::Handlers::StoryHandler,
|
|
92
|
+
on_callback_query: GrubY::Handlers::CallbackQueryHandler,
|
|
93
|
+
on_chat_boost: GrubY::Handlers::ChatBoostHandler,
|
|
94
|
+
on_chat_join_request: GrubY::Handlers::ChatJoinRequestHandler,
|
|
95
|
+
on_chat_member_updated: GrubY::Handlers::ChatMemberUpdatedHandler,
|
|
96
|
+
on_chosen_inline_result: GrubY::Handlers::ChosenInlineResultHandler,
|
|
97
|
+
on_inline_query: GrubY::Handlers::InlineQueryHandler,
|
|
98
|
+
on_poll: GrubY::Handlers::PollHandler,
|
|
99
|
+
on_pre_checkout_query: GrubY::Handlers::PreCheckoutQueryHandler,
|
|
100
|
+
on_purchased_paid_media: GrubY::Handlers::PurchasedPaidMediaHandler,
|
|
101
|
+
on_shipping_query: GrubY::Handlers::ShippingQueryHandler,
|
|
102
|
+
on_user_status: GrubY::Handlers::UserStatusHandler,
|
|
103
|
+
on_start: GrubY::Handlers::StartHandler,
|
|
104
|
+
on_stop: GrubY::Handlers::StopHandler,
|
|
105
|
+
on_connect: GrubY::Handlers::ConnectHandler,
|
|
106
|
+
on_disconnect: GrubY::Handlers::DisconnectHandler,
|
|
107
|
+
on_managed_bot: GrubY::Handlers::ManagedBotUpdatedHandler,
|
|
108
|
+
on_raw_update: GrubY::Handlers::RawUpdateHandler
|
|
109
|
+
}.each do |decorator_name, klass|
|
|
110
|
+
define_method(decorator_name) do |filters = nil, group: 0, &block|
|
|
111
|
+
add_handler(klass.new(block, filters: filters, group: group))
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def on_error(exceptions = nil, filters = nil, group: 0, &block)
|
|
116
|
+
add_handler(
|
|
117
|
+
GrubY::Handlers::ErrorHandler.new(
|
|
118
|
+
block,
|
|
119
|
+
filters: filters,
|
|
120
|
+
group: group,
|
|
121
|
+
exceptions: exceptions
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def use(&block)
|
|
127
|
+
@bot.use(&block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def use_plugin(path)
|
|
131
|
+
@bot.use_plugin(path)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def process_update(update)
|
|
135
|
+
@bot.process_update(update)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def stop_transmission
|
|
139
|
+
true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def export_session_string
|
|
143
|
+
@session.to_h.to_json
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def set_parse_mode(mode)
|
|
147
|
+
@session.set("__parse_mode__", mode.to_s)
|
|
148
|
+
mode
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def get_parse_mode
|
|
152
|
+
@session.get("__parse_mode__")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def set_dc(dc_id, config = {})
|
|
156
|
+
@session.set("dc:#{dc_id}", config)
|
|
157
|
+
config
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def get_dc_option(dc_id)
|
|
161
|
+
@session.get("dc:#{dc_id}")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def get_session
|
|
165
|
+
@session
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def get_file(file_id)
|
|
169
|
+
@api.get_file(file_id)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def build_message(data)
|
|
173
|
+
GrubY::Message.new(data, api: @api, client: self)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_chat(data)
|
|
177
|
+
GrubY::Chat.new(data, api: @api, client: self)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def build_user(data)
|
|
181
|
+
GrubY::User.new(data, api: @api, client: self)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def idle
|
|
185
|
+
@idle = true
|
|
186
|
+
trap_signals
|
|
187
|
+
sleep 0.3 while @idle
|
|
188
|
+
ensure
|
|
189
|
+
@idle = false
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.compose(*clients, &block)
|
|
193
|
+
clients.each(&:start)
|
|
194
|
+
block.call if block
|
|
195
|
+
clients.each(&:idle)
|
|
196
|
+
ensure
|
|
197
|
+
clients.each(&:stop)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
[
|
|
201
|
+
:send_message, :forward_message, :forward_messages, :forward_media_group,
|
|
202
|
+
:copy_message, :copy_messages, :copy_media_group, :send_photo, :send_audio,
|
|
203
|
+
:send_document, :send_video, :send_animation, :send_voice, :send_video_note,
|
|
204
|
+
:send_media_group, :send_paid_media, :send_location, :send_venue,
|
|
205
|
+
:send_contact, :send_poll, :send_checklist, :send_dice, :send_message_draft,
|
|
206
|
+
:send_chat_action, :set_message_reaction, :send_reaction,
|
|
207
|
+
:answer_callback_query, :edit_message_text, :delete_message,
|
|
208
|
+
:delete_messages, :get_updates, :download_file
|
|
209
|
+
].each do |api_method|
|
|
210
|
+
define_method(api_method) do |*args, **kwargs|
|
|
211
|
+
@api.public_send(api_method, *args, **kwargs)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
216
|
+
return super unless @api.respond_to?(name)
|
|
217
|
+
|
|
218
|
+
@api.public_send(name, *args, **kwargs, &block)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def respond_to_missing?(name, include_private = false)
|
|
222
|
+
@api.respond_to?(name) || super
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def register_handler_object(handler)
|
|
228
|
+
wrapped = proc do |ctx|
|
|
229
|
+
payload = handler.extract(ctx)
|
|
230
|
+
if handler.filters
|
|
231
|
+
next unless handler.filters.call(handler, self, payload)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
if handler.is_a?(GrubY::Handlers::ErrorHandler)
|
|
235
|
+
error = payload
|
|
236
|
+
allowed = Array(handler.exceptions).compact
|
|
237
|
+
next if !allowed.empty? && !allowed.any? { |klass| error.is_a?(klass) }
|
|
238
|
+
invoke_handler_callback(handler.callback, error, ctx)
|
|
239
|
+
else
|
|
240
|
+
invoke_handler_callback(handler.callback, payload, ctx)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
@bot.on(handler.event, nil, group: handler.group, &wrapped)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def invoke_handler_callback(callback, payload, ctx)
|
|
248
|
+
return unless callback
|
|
249
|
+
if payload.nil? && ctx.update.is_a?(Hash) && ctx.update.empty?
|
|
250
|
+
case callback.arity
|
|
251
|
+
when 0 then callback.call
|
|
252
|
+
when 1 then callback.call(self)
|
|
253
|
+
else callback.call(self, nil, ctx)
|
|
254
|
+
end
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
case callback.arity
|
|
259
|
+
when 0
|
|
260
|
+
callback.call
|
|
261
|
+
when 1
|
|
262
|
+
callback.call(payload)
|
|
263
|
+
when 2
|
|
264
|
+
callback.call(self, payload)
|
|
265
|
+
else
|
|
266
|
+
callback.call(self, payload, ctx)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def start_polling
|
|
271
|
+
offset = nil
|
|
272
|
+
@polling_thread = Thread.new do
|
|
273
|
+
while @running
|
|
274
|
+
begin
|
|
275
|
+
updates = @api.get_updates(offset, timeout: @polling_timeout) || []
|
|
276
|
+
updates.each do |update|
|
|
277
|
+
offset = update["update_id"].to_i + 1
|
|
278
|
+
process_update(update)
|
|
279
|
+
end
|
|
280
|
+
rescue => e
|
|
281
|
+
@bot.emit(:error, { "error" => e })
|
|
282
|
+
warn "[CLIENT] polling error: #{e.class}: #{e.message}"
|
|
283
|
+
sleep 1
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def trap_signals
|
|
290
|
+
%w[INT TERM].each do |sig|
|
|
291
|
+
Signal.trap(sig) { @idle = false }
|
|
292
|
+
rescue ArgumentError
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
require_relative "types/user"
|
|
2
|
+
require_relative "file_stream"
|
|
3
|
+
require_relative "types/message"
|
|
4
|
+
require_relative "types/bound_entities"
|
|
5
|
+
require_relative "types/base_object"
|
|
6
|
+
|
|
7
|
+
module GrubY
|
|
8
|
+
class Context
|
|
9
|
+
attr_reader :update, :api, :session
|
|
10
|
+
|
|
11
|
+
def initialize(update, api, session = nil)
|
|
12
|
+
@update = update
|
|
13
|
+
@api = api
|
|
14
|
+
@session = session
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def message
|
|
18
|
+
data = primary_message_payload
|
|
19
|
+
Message.new(data, api: @api) if data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def edited_message
|
|
23
|
+
data = @update["edited_message"]
|
|
24
|
+
Message.new(data, api: @api) if data
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def business_message
|
|
28
|
+
data = @update["business_message"]
|
|
29
|
+
Message.new(data, api: @api) if data
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def edited_business_message
|
|
33
|
+
data = @update["edited_business_message"]
|
|
34
|
+
Message.new(data, api: @api) if data
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def deleted_messages
|
|
38
|
+
list = @update["deleted_messages"]
|
|
39
|
+
Array(list).map { |m| Message.new(m, api: @api) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def deleted_business_messages
|
|
43
|
+
list = @update["deleted_business_messages"]
|
|
44
|
+
Array(list).map { |m| Message.new(m, api: @api) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def text
|
|
48
|
+
primary_message_payload&.dig("text")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def chat_id
|
|
52
|
+
primary_message_payload&.dig("chat", "id")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def from
|
|
56
|
+
data = user_data
|
|
57
|
+
User.new(data, api: @api) if data
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def poll(question, options)
|
|
61
|
+
@api.send_poll(chat_id, question, options)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def callback_query
|
|
65
|
+
data = update["callback_query"]
|
|
66
|
+
CallbackQuery.new(data, api: @api) if data
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def inline_query
|
|
70
|
+
data = update["inline_query"]
|
|
71
|
+
InlineQuery.new(data, api: @api) if data
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def pre_checkout_query
|
|
75
|
+
data = update["pre_checkout_query"]
|
|
76
|
+
PreCheckoutQuery.new(data, api: @api) if data
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def shipping_query
|
|
80
|
+
data = update["shipping_query"]
|
|
81
|
+
ShippingQuery.new(data, api: @api) if data
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def chat_join_request
|
|
85
|
+
data = update["chat_join_request"]
|
|
86
|
+
ChatJoinRequest.new(data, api: @api) if data
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def business_connection
|
|
90
|
+
data = @update["business_connection"]
|
|
91
|
+
wrap_type("BusinessConnection", data)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def message_reaction_count
|
|
95
|
+
data = @update["message_reaction_count"] || @update["message_reaction_count_updated"]
|
|
96
|
+
wrap_type("MessageReactionCountUpdated", data)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def message_reaction
|
|
100
|
+
data = @update["message_reaction"] || @update["message_reaction_updated"]
|
|
101
|
+
wrap_type("MessageReactionUpdated", data)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def chat_boost
|
|
105
|
+
data = @update["chat_boost"] || @update["chat_boost_updated"]
|
|
106
|
+
wrap_type("ChatBoostUpdated", data)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def chat_member_updated
|
|
110
|
+
data = @update["chat_member_updated"] || @update["chat_member"]
|
|
111
|
+
wrap_type("ChatMemberUpdated", data)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def chosen_inline_result
|
|
115
|
+
data = @update["chosen_inline_result"]
|
|
116
|
+
wrap_type("ChosenInlineResult", data)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def poll_update
|
|
120
|
+
data = @update["poll"]
|
|
121
|
+
wrap_type("Poll", data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def purchased_paid_media
|
|
125
|
+
data = @update["purchased_paid_media"]
|
|
126
|
+
wrap_type("PurchasedPaidMedia", data)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def story_update
|
|
130
|
+
data = @update["story"]
|
|
131
|
+
Story.new(data, api: @api) if data
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def user_status
|
|
135
|
+
data = @update["user_status"] || @update["user"]
|
|
136
|
+
User.new(data, api: @api) if data
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def managed_bot_updated
|
|
140
|
+
data = @update["managed_bot_updated"] || @update["managed_bot"]
|
|
141
|
+
wrap_type("ManagedBotUpdated", data)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def error_payload
|
|
145
|
+
@update["error"]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def user
|
|
149
|
+
from
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def session_data
|
|
153
|
+
return {} unless @session && user
|
|
154
|
+
|
|
155
|
+
@session.get(user.id)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def set_session(value)
|
|
159
|
+
return unless @session && user
|
|
160
|
+
|
|
161
|
+
@session.set(user.id, value)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def reply(text, keyboard = nil)
|
|
165
|
+
params = {}
|
|
166
|
+
params[:reply_markup] = keyboard if keyboard
|
|
167
|
+
@api.send_message(chat_id, text, **params)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def answer_callback(text = nil, show_alert: false)
|
|
171
|
+
id = callback_query&.id
|
|
172
|
+
return unless id
|
|
173
|
+
|
|
174
|
+
params = { callback_query_id: id, show_alert: show_alert }
|
|
175
|
+
params[:text] = text if text
|
|
176
|
+
@api.raw("answerCallbackQuery", params)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def raw(method, params = {})
|
|
180
|
+
@api.raw(method, params)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def command
|
|
184
|
+
t = text
|
|
185
|
+
return nil unless t&.start_with?("/")
|
|
186
|
+
|
|
187
|
+
token = t.split(/\s+/, 2).first
|
|
188
|
+
token.sub(%r{^/}, "").split("@", 2).first
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def command_args
|
|
192
|
+
t = text
|
|
193
|
+
return [] unless t
|
|
194
|
+
|
|
195
|
+
_, rest = t.split(/\s+/, 2)
|
|
196
|
+
return [] unless rest
|
|
197
|
+
|
|
198
|
+
rest.strip.split(/\s+/)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def file_id
|
|
202
|
+
data = primary_message_payload
|
|
203
|
+
return nil unless data
|
|
204
|
+
|
|
205
|
+
if data["photo"]
|
|
206
|
+
data["photo"].last["file_id"]
|
|
207
|
+
elsif data["video"]
|
|
208
|
+
data["video"]["file_id"]
|
|
209
|
+
elsif data["audio"]
|
|
210
|
+
data["audio"]["file_id"]
|
|
211
|
+
elsif data["document"]
|
|
212
|
+
data["document"]["file_id"]
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def download(file_name = "file.dat")
|
|
217
|
+
f = api.get_file(file_id)
|
|
218
|
+
api.download_file(f["file_path"], file_name)
|
|
219
|
+
file_name
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
private
|
|
223
|
+
|
|
224
|
+
def primary_message_payload
|
|
225
|
+
@update["message"] || @update.dig("callback_query", "message")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def user_data
|
|
229
|
+
@update.dig("message", "from") || @update.dig("callback_query", "from")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def wrap_type(type_name, data)
|
|
233
|
+
return nil unless data
|
|
234
|
+
return data unless GrubY.const_defined?(type_name)
|
|
235
|
+
|
|
236
|
+
klass = GrubY.const_get(type_name)
|
|
237
|
+
return data unless klass <= GrubY::BaseObject
|
|
238
|
+
|
|
239
|
+
klass.new(data, api: @api)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module GrubY
|
|
2
|
+
class Dispatcher
|
|
3
|
+
def initialize
|
|
4
|
+
@handlers = {}
|
|
5
|
+
@next_handler_id = 1
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def on(event, filter = nil, group: 0, &block)
|
|
9
|
+
id = @next_handler_id
|
|
10
|
+
@next_handler_id += 1
|
|
11
|
+
(@handlers[event] ||= []) << { id: id, filter: filter, block: block, group: group.to_i }
|
|
12
|
+
@handlers[event].sort_by! { |h| h[:group] }
|
|
13
|
+
id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def off(event, handler_id = nil, &block)
|
|
17
|
+
return false unless @handlers[event]
|
|
18
|
+
|
|
19
|
+
before = @handlers[event].length
|
|
20
|
+
@handlers[event].reject! do |entry|
|
|
21
|
+
if handler_id
|
|
22
|
+
entry[:id] == handler_id
|
|
23
|
+
elsif block
|
|
24
|
+
entry[:block] == block
|
|
25
|
+
else
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
before != @handlers[event].length
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def trigger(event, ctx)
|
|
33
|
+
return unless @handlers[event]
|
|
34
|
+
|
|
35
|
+
@handlers[event].each do |entry|
|
|
36
|
+
filter = entry[:filter]
|
|
37
|
+
handler = entry[:block]
|
|
38
|
+
if filter
|
|
39
|
+
pass = begin
|
|
40
|
+
if defined?(GrubY::Filters::Filter) && filter.is_a?(GrubY::Filters::Filter)
|
|
41
|
+
filter.call(nil, nil, ctx)
|
|
42
|
+
elsif filter.respond_to?(:call)
|
|
43
|
+
filter.call(ctx)
|
|
44
|
+
else
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
rescue ArgumentError
|
|
48
|
+
filter.call(nil, nil, ctx)
|
|
49
|
+
end
|
|
50
|
+
next unless pass
|
|
51
|
+
end
|
|
52
|
+
begin
|
|
53
|
+
handler.call(ctx)
|
|
54
|
+
rescue => e
|
|
55
|
+
warn "[DISPATCHER] #{event} handler failed: #{e.class}: #{e.message}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/grubY/enums.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module GrubY
|
|
2
|
+
module Enums
|
|
3
|
+
DEFINITIONS = {
|
|
4
|
+
BlockList: %w[main stories],
|
|
5
|
+
BusinessSchedule: %w[always offline custom],
|
|
6
|
+
ButtonStyle: %w[default primary danger success],
|
|
7
|
+
ChatAction: %w[typing upload_photo record_video upload_video record_voice upload_voice upload_document choose_sticker find_location record_video_note upload_video_note],
|
|
8
|
+
ChatEventAction: %w[create edit delete pin unpin join leave invite restrict promote],
|
|
9
|
+
ChatJoinType: %w[by_link by_request by_invite by_username],
|
|
10
|
+
ChatMemberStatus: %w[creator administrator member restricted left kicked],
|
|
11
|
+
ChatMembersFilter: %w[recent administrators contacts bots banned restricted search],
|
|
12
|
+
ChatType: %w[private bot group supergroup channel direct forum],
|
|
13
|
+
ClientPlatform: %w[ruby linux windows macos android ios web],
|
|
14
|
+
FolderColor: %w[blue cyan green orange pink purple red yellow],
|
|
15
|
+
MessageEntityType: %w[mention hashtag cashtag bot_command url email phone_number bold italic underline strikethrough spoiler code pre text_link text_mention custom_emoji],
|
|
16
|
+
MessageMediaType: %w[text photo video animation audio voice video_note document sticker contact location venue poll dice game story paid_media],
|
|
17
|
+
MessageOriginType: %w[user hidden_user chat channel],
|
|
18
|
+
MessageServiceType: %w[new_chat_members left_chat_member new_chat_title new_chat_photo delete_chat_photo group_chat_created supergroup_chat_created channel_chat_created migrate_to_chat_id migrate_from_chat_id pinned_message game_score video_chat_started video_chat_ended video_chat_members_invited successful_payment],
|
|
19
|
+
MessagesFilter: %w[empty photo video document url gif voice music chat_photo phone_call round_video video_note voice_note photo_video mention unread mention_me pinned],
|
|
20
|
+
NextCodeType: %w[sms call flash_call missed_call fragment app],
|
|
21
|
+
PaidReactionPrivacy: %w[anonymous default sender],
|
|
22
|
+
ParseMode: %w[markdown markdown_v2 html],
|
|
23
|
+
PhoneCallDiscardReason: %w[missed disconnect hangup busy],
|
|
24
|
+
PhoneNumberCodeType: %w[sms call flash_call missed_call fragment app],
|
|
25
|
+
PollType: %w[regular quiz],
|
|
26
|
+
PrivacyKey: %w[last_seen phone_number profile_photo forwards calls p2p_invite link_in_bio birthday gifts voice_messages about],
|
|
27
|
+
ProfileColor: %w[blue cyan green orange pink purple red yellow],
|
|
28
|
+
ProfileTab: %w[profile gifts media links files voice],
|
|
29
|
+
ReplyColor: %w[blue cyan green orange pink purple red yellow],
|
|
30
|
+
SentCodeType: %w[sms call flash_call missed_call fragment app firebase email],
|
|
31
|
+
StoriesPrivacyRules: %w[everyone contacts close_friends selected],
|
|
32
|
+
UserStatus: %w[online offline recently last_week last_month long_time_ago],
|
|
33
|
+
UpgradedGiftOrigin: %w[upgrade gift_code transfer craft purchase],
|
|
34
|
+
GiftAttributeType: %w[model symbol backdrop rarity id original_details],
|
|
35
|
+
MediaAreaType: %w[venue location suggested_reaction link weather],
|
|
36
|
+
PrivacyRuleType: %w[allow_all allow_contacts allow_users allow_chats allow_close_friends allow_premium disallow_all disallow_contacts disallow_users disallow_chats disallow_bots],
|
|
37
|
+
GiftForResaleOrder: %w[newest oldest price_asc price_desc rarity_asc rarity_desc],
|
|
38
|
+
GiftPurchaseOfferState: %w[pending accepted rejected expired canceled],
|
|
39
|
+
GiftType: %w[regular upgraded unique collectible],
|
|
40
|
+
PaymentFormType: %w[invoice stars ton gift_upgrade],
|
|
41
|
+
StickerType: %w[regular mask custom_emoji],
|
|
42
|
+
MaskPointType: %w[forehead eyes mouth chin],
|
|
43
|
+
SuggestedPostRefundReason: %w[not_published rejected timeout canceled],
|
|
44
|
+
SuggestedPostState: %w[pending approved declined paid refunded failed]
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
DEFINITIONS.each do |enum_name, values|
|
|
48
|
+
enum_module = Module.new
|
|
49
|
+
values.each do |value|
|
|
50
|
+
const_name = value.upcase.gsub(/[^A-Z0-9]+/, "_")
|
|
51
|
+
enum_module.const_set(const_name, value)
|
|
52
|
+
end
|
|
53
|
+
const_set(enum_name, enum_module)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module GroupCallUpdateType
|
|
57
|
+
UPDATE_GROUP_CALL = "updateGroupCall"
|
|
58
|
+
UPDATE_GROUP_CALL_PARTICIPANTS = "updateGroupCallParticipants"
|
|
59
|
+
UPDATE_GROUP_CALL_SIGNALING_DATA = "updateGroupCallSignalingData"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
module GrubY
|
|
4
|
+
class FileStream
|
|
5
|
+
def self.download(url, dest)
|
|
6
|
+
uri = URI(url)
|
|
7
|
+
|
|
8
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
9
|
+
http.request_get(uri) do |res|
|
|
10
|
+
File.open(dest, "wb") do |f|
|
|
11
|
+
res.read_body { |chunk| f.write(chunk) }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|