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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +6 -0
  4. data/Rakefile +51 -0
  5. data/config/config.rb +5 -0
  6. data/example/bot.rb +160 -0
  7. data/example/generate_user_session.rb +81 -0
  8. data/example/ntgcalls.rb +80 -0
  9. data/example/tdlib.rb +77 -0
  10. data/example/userbot.rb +133 -0
  11. data/lib/grubY/api.rb +363 -0
  12. data/lib/grubY/async.rb +13 -0
  13. data/lib/grubY/bot.rb +90 -0
  14. data/lib/grubY/bound.rb +14 -0
  15. data/lib/grubY/client.rb +296 -0
  16. data/lib/grubY/context.rb +242 -0
  17. data/lib/grubY/dispatcher.rb +60 -0
  18. data/lib/grubY/enums.rb +62 -0
  19. data/lib/grubY/exception.rb +5 -0
  20. data/lib/grubY/file_stream.rb +17 -0
  21. data/lib/grubY/filters.rb +292 -0
  22. data/lib/grubY/group_manager.rb +213 -0
  23. data/lib/grubY/handlers.rb +170 -0
  24. data/lib/grubY/keyboard.rb +49 -0
  25. data/lib/grubY/media.rb +38 -0
  26. data/lib/grubY/middleware.rb +18 -0
  27. data/lib/grubY/ntgcalls/native.rb +100 -0
  28. data/lib/grubY/ntgcalls.rb +478 -0
  29. data/lib/grubY/plugin.rb +14 -0
  30. data/lib/grubY/raw.rb +25 -0
  31. data/lib/grubY/raw_types.rb +57 -0
  32. data/lib/grubY/retry.rb +14 -0
  33. data/lib/grubY/server.rb +18 -0
  34. data/lib/grubY/session.rb +30 -0
  35. data/lib/grubY/tdlib/client.rb +565 -0
  36. data/lib/grubY/tdlib/client_manager.rb +37 -0
  37. data/lib/grubY/tdlib/decorators.rb +17 -0
  38. data/lib/grubY/tdlib/errors.rb +6 -0
  39. data/lib/grubY/tdlib/group_manager.rb +58 -0
  40. data/lib/grubY/tdlib/native.rb +72 -0
  41. data/lib/grubY/tdlib/schema_builder.rb +237 -0
  42. data/lib/grubY/tdlib/tdjson.rb +49 -0
  43. data/lib/grubY/tdlib/user_session.rb +69 -0
  44. data/lib/grubY/types/base_object.rb +72 -0
  45. data/lib/grubY/types/bound_entities.rb +245 -0
  46. data/lib/grubY/types/chat.rb +93 -0
  47. data/lib/grubY/types/chat_info.rb +31 -0
  48. data/lib/grubY/types/extra.rb +251 -0
  49. data/lib/grubY/types/message.rb +295 -0
  50. data/lib/grubY/types/message_entity.rb +14 -0
  51. data/lib/grubY/types/registry.rb +67 -0
  52. data/lib/grubY/types/user.rb +31 -0
  53. data/lib/grubY/webapp.rb +22 -0
  54. data/lib/grubY.rb +41 -0
  55. data/plugins/logger.rb +7 -0
  56. data/plugins/media_tools.rb +13 -0
  57. metadata +110 -0
@@ -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
@@ -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,5 @@
1
+ module GrubY
2
+ class Error < StandardError; end
3
+ class ConfigurationError < Error; end
4
+ class DependencyError < Error; end
5
+ 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