telegem 1.0.6 → 2.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.
data/lib/api/types.rb CHANGED
@@ -3,16 +3,19 @@ module Telegem
3
3
  class BaseType
4
4
  def initialize(data)
5
5
  @_raw_data = data || {}
6
+ @_accessors_defined = {}
6
7
  end
7
8
 
8
9
  def method_missing(name, *args)
9
- key = name.to_s
10
- return @_raw_data[key] if @_raw_data.key?(key)
10
+ return super if args.any? || block_given?
11
11
 
12
- camel_key = snake_to_camel(key)
13
- return @_raw_data[camel_key] if @_raw_data.key?(camel_key)
12
+ define_accessor(name)
14
13
 
15
- super
14
+ if respond_to?(name)
15
+ send(name)
16
+ else
17
+ super
18
+ end
16
19
  end
17
20
 
18
21
  def respond_to_missing?(name, include_private = false)
@@ -21,30 +24,66 @@ module Telegem
21
24
  @_raw_data.key?(key) || @_raw_data.key?(camel_key) || super
22
25
  end
23
26
 
27
+ def to_h
28
+ @_raw_data.dup
29
+ end
30
+
31
+ alias_method :to_hash, :to_h
32
+
33
+ def inspect
34
+ "#<#{self.class.name} #{@_raw_data.inspect}>"
35
+ end
36
+
37
+ def to_s
38
+ inspect
39
+ end
40
+
24
41
  attr_reader :_raw_data
25
42
 
26
43
  private
27
44
 
45
+ def define_accessor(name)
46
+ return if @_accessors_defined[name]
47
+
48
+ key = name.to_s
49
+ camel_key = snake_to_camel(key)
50
+
51
+ if @_raw_data.key?(key)
52
+ define_singleton_method(name) { @_raw_data[key] }
53
+ elsif @_raw_data.key?(camel_key)
54
+ define_singleton_method(name) { @_raw_data[camel_key] }
55
+ else
56
+ define_singleton_method(name) do
57
+ raise NoMethodError,
58
+ "undefined method `#{name}' for #{self.class} with keys: #{@_raw_data.keys}"
59
+ end
60
+ end
61
+
62
+ @_accessors_defined[name] = true
63
+ end
64
+
28
65
  def snake_to_camel(str)
29
66
  str.gsub(/_([a-z])/) { $1.upcase }
30
67
  end
68
+
69
+ def camel_to_snake(str)
70
+ str.gsub(/([A-Z])/) { "_#{$1.downcase}" }.sub(/^_/, '')
71
+ end
31
72
  end
32
73
 
33
74
  class User < BaseType
34
- attr_reader :id, :is_bot, :first_name, :last_name, :username,
35
- :can_join_groups, :can_read_all_group_messages, :supports_inline_queries
75
+ COMMON_FIELDS = %w[id is_bot first_name last_name username
76
+ can_join_groups can_read_all_group_messages
77
+ supports_inline_queries language_code
78
+ is_premium added_to_attachment_menu
79
+ can_connect_to_business].freeze
36
80
 
37
81
  def initialize(data)
38
82
  super(data)
39
83
 
40
- @id = data['id']
41
- @is_bot = data['is_bot']
42
- @first_name = data['first_name']
43
- @last_name = data['last_name']
44
- @username = data['username']
45
- @can_join_groups = data['can_join_groups']
46
- @can_read_all_group_messages = data['can_read_all_group_messages']
47
- @supports_inline_queries = data['supports_inline_queries']
84
+ COMMON_FIELDS.each do |field|
85
+ define_accessor(field.to_sym)
86
+ end
48
87
  end
49
88
 
50
89
  def full_name
@@ -52,20 +91,35 @@ module Telegem
52
91
  end
53
92
 
54
93
  def mention
55
- username ? "@#{username}" : first_name
94
+ if username
95
+ "@#{username}"
96
+ elsif first_name
97
+ first_name
98
+ else
99
+ "User ##{id}"
100
+ end
101
+ end
102
+
103
+ def to_s
104
+ full_name
56
105
  end
57
106
  end
58
107
 
59
108
  class Chat < BaseType
60
- attr_reader :id, :type, :username, :title
109
+ COMMON_FIELDS = %w[id type username title first_name last_name
110
+ photo bio has_private_forwards
111
+ has_restricted_voice_and_video_messages
112
+ description invite_link pinned_message
113
+ permissions slow_mode_delay message_auto_delete_time
114
+ has_protected_content sticker_set_name
115
+ can_set_sticker_set linked_chat_id location].freeze
61
116
 
62
117
  def initialize(data)
63
118
  super(data)
64
119
 
65
- @id = data['id']
66
- @type = data['type']
67
- @username = data['username']
68
- @title = data['title']
120
+ COMMON_FIELDS.each do |field|
121
+ define_accessor(field.to_sym)
122
+ end
69
123
  end
70
124
 
71
125
  def private?
@@ -83,58 +137,62 @@ module Telegem
83
137
  def channel?
84
138
  type == 'channel'
85
139
  end
140
+
141
+ def to_s
142
+ title || username || "Chat ##{id}"
143
+ end
86
144
  end
87
145
 
88
146
  class MessageEntity < BaseType
89
- attr_reader :type, :offset, :length, :url, :user, :language
147
+ COMMON_FIELDS = %w[type offset length url user language
148
+ custom_emoji_id].freeze
90
149
 
91
150
  def initialize(data)
92
151
  super(data)
93
152
 
94
- @type = data['type']
95
- @offset = data['offset']
96
- @length = data['length']
97
- @url = data['url']
98
- @user = User.new(data['user']) if data['user']
99
- @language = data['language']
153
+ COMMON_FIELDS.each do |field|
154
+ define_accessor(field.to_sym)
155
+ end
156
+
157
+ if @_raw_data['user'] && !@_raw_data['user'].is_a?(User)
158
+ @_raw_data['user'] = User.new(@_raw_data['user'])
159
+ end
100
160
  end
101
161
  end
102
162
 
103
163
  class Message < BaseType
104
- attr_reader :message_id, :from, :chat, :date, :text, :entities,
105
- :reply_markup, :via_bot, :forward_from, :forward_from_chat
164
+ COMMON_FIELDS = %w[message_id from chat date edit_date
165
+ text caption entities caption_entities
166
+ audio document photo sticker video voice
167
+ video_note contact location venue
168
+ new_chat_members left_chat_member
169
+ new_chat_title new_chat_photo
170
+ delete_chat_photo group_chat_created
171
+ supergroup_chat_created channel_chat_created
172
+ migrate_to_chat_id migrate_from_chat_id
173
+ pinned_message invoice successful_payment
174
+ connected_website reply_markup via_bot
175
+ forward_from forward_from_chat
176
+ forward_from_message_id forward_signature
177
+ forward_sender_name forward_date reply_to_message
178
+ media_group_id author_signature
179
+ has_protected_content].freeze
106
180
 
107
181
  def initialize(data)
108
182
  super(data)
109
183
 
110
- @message_id = data['message_id']
111
- @from = User.new(data['from']) if data['from']
112
- @chat = Chat.new(data['chat']) if data['chat']
113
- @date = Time.at(data['date']) if data['date']
114
- @text = data['text']
115
-
116
- if data['entities']
117
- @entities = data['entities'].map { |e| MessageEntity.new(e) }
184
+ COMMON_FIELDS.each do |field|
185
+ define_accessor(field.to_sym)
118
186
  end
119
187
 
120
- @reply_markup = data['reply_markup']
121
- @via_bot = User.new(data['via_bot']) if data['via_bot']
122
- @forward_from = User.new(data['forward_from']) if data['forward_from']
123
- @forward_from_chat = Chat.new(data['forward_from_chat']) if data['forward_from_chat']
188
+ convert_complex_fields
124
189
  end
125
190
 
126
- # FIXED: Proper command detection
127
191
  def command?
128
- return false unless text
129
- return false unless entities
130
-
131
- # Find a "bot_command" entity
132
- command_entity = entities.find { |e| e.type == 'bot_command' }
133
- return false unless command_entity
192
+ return false unless text && entities
134
193
 
135
- # Extract the command text
136
- command_text = text[command_entity.offset, command_entity.length]
137
- command_text&.start_with?('/')
194
+ entities.any? { |e| e.type == 'bot_command' &&
195
+ text[e.offset, e.length]&.start_with?('/') }
138
196
  end
139
197
 
140
198
  def command_name
@@ -144,9 +202,10 @@ module Telegem
144
202
  return nil unless command_entity
145
203
 
146
204
  cmd = text[command_entity.offset, command_entity.length]
147
- cmd = cmd[1..-1] # Remove "/"
148
- cmd = cmd.split('@').first # Remove bot username
149
- cmd
205
+ return nil if cmd.nil? || cmd.length <= 1
206
+
207
+ cmd = cmd[1..-1]
208
+ cmd.split('@').first.strip
150
209
  end
151
210
 
152
211
  def command_args
@@ -155,35 +214,192 @@ module Telegem
155
214
  command_entity = entities.find { |e| e.type == 'bot_command' }
156
215
  return nil unless command_entity
157
216
 
158
- # Text after the command entity
159
217
  args_start = command_entity.offset + command_entity.length
160
- text[args_start..-1]&.strip
218
+ remaining = text[args_start..-1]
219
+
220
+ next_entity = entities.select { |e| e.offset >= args_start }
221
+ .min_by(&:offset)
222
+
223
+ if next_entity
224
+ args_end = next_entity.offset - 1
225
+ text[args_start..args_end]&.strip
226
+ else
227
+ remaining&.strip
228
+ end
229
+ end
230
+
231
+ def reply?
232
+ !!reply_to_message
233
+ end
234
+
235
+ def has_media?
236
+ !!(audio || document || photo || video || voice || video_note || sticker)
237
+ end
238
+
239
+ def media_type
240
+ return :audio if audio
241
+ return :document if document
242
+ return :photo if photo
243
+ return :video if video
244
+ return :voice if voice
245
+ return :video_note if video_note
246
+ return :sticker if sticker
247
+ nil
248
+ end
249
+
250
+ private
251
+
252
+ def convert_complex_fields
253
+ if @_raw_data['date'] && !@_raw_data['date'].is_a?(Time)
254
+ @_raw_data['date'] = Time.at(@_raw_data['date'])
255
+ end
256
+
257
+ if @_raw_data['edit_date'] && !@_raw_data['edit_date'].is_a?(Time)
258
+ @_raw_data['edit_date'] = Time.at(@_raw_data['edit_date'])
259
+ end
260
+
261
+ if @_raw_data['forward_date'] && !@_raw_data['forward_date'].is_a?(Time)
262
+ @_raw_data['forward_date'] = Time.at(@_raw_data['forward_date'])
263
+ end
264
+
265
+ @_raw_data['from'] = User.new(@_raw_data['from']) if @_raw_data['from'] && !@_raw_data['from'].is_a?(User)
266
+ @_raw_data['chat'] = Chat.new(@_raw_data['chat']) if @_raw_data['chat'] && !@_raw_data['chat'].is_a?(Chat)
267
+ @_raw_data['via_bot'] = User.new(@_raw_data['via_bot']) if @_raw_data['via_bot'] && !@_raw_data['via_bot'].is_a?(User)
268
+ @_raw_data['forward_from'] = User.new(@_raw_data['forward_from']) if @_raw_data['forward_from'] && !@_raw_data['forward_from'].is_a?(User)
269
+ @_raw_data['forward_from_chat'] = Chat.new(@_raw_data['forward_from_chat']) if @_raw_data['forward_from_chat'] && !@_raw_data['forward_from_chat'].is_a?(Chat)
270
+ @_raw_data['left_chat_member'] = User.new(@_raw_data['left_chat_member']) if @_raw_data['left_chat_member'] && !@_raw_data['left_chat_member'].is_a?(User)
271
+
272
+ if @_raw_data['entities'] && @_raw_data['entities'].is_a?(Array)
273
+ @_raw_data['entities'] = @_raw_data['entities'].map do |e|
274
+ e.is_a?(MessageEntity) ? e : MessageEntity.new(e)
275
+ end
276
+ end
277
+
278
+ if @_raw_data['caption_entities'] && @_raw_data['caption_entities'].is_a?(Array)
279
+ @_raw_data['caption_entities'] = @_raw_data['caption_entities'].map do |e|
280
+ e.is_a?(MessageEntity) ? e : MessageEntity.new(e)
281
+ end
282
+ end
283
+
284
+ if @_raw_data['reply_to_message'] && !@_raw_data['reply_to_message'].is_a?(Message)
285
+ @_raw_data['reply_to_message'] = Message.new(@_raw_data['reply_to_message'])
286
+ end
287
+
288
+ if @_raw_data['pinned_message'] && !@_raw_data['pinned_message'].is_a?(Message)
289
+ @_raw_data['pinned_message'] = Message.new(@_raw_data['pinned_message'])
290
+ end
291
+
292
+ if @_raw_data['new_chat_members'] && @_raw_data['new_chat_members'].is_a?(Array)
293
+ @_raw_data['new_chat_members'] = @_raw_data['new_chat_members'].map do |u|
294
+ u.is_a?(User) ? u : User.new(u)
295
+ end
296
+ end
161
297
  end
162
298
  end
163
299
 
164
300
  class CallbackQuery < BaseType
165
- attr_reader :id, :from, :message, :data, :chat_instance
301
+ COMMON_FIELDS = %w[id from message inline_message_id chat_instance data game_short_name].freeze
166
302
 
167
303
  def initialize(data)
168
304
  super(data)
169
305
 
170
- @id = data['id']
171
- @from = User.new(data['from']) if data['from']
172
- @message = Message.new(data['message']) if data['message']
173
- @data = data['data']
174
- @chat_instance = data['chat_instance']
306
+ COMMON_FIELDS.each do |field|
307
+ define_accessor(field.to_sym)
308
+ end
309
+
310
+ if @_raw_data['from'] && !@_raw_data['from'].is_a?(User)
311
+ @_raw_data['from'] = User.new(@_raw_data['from'])
312
+ end
313
+
314
+ if @_raw_data['message'] && !@_raw_data['message'].is_a?(Message)
315
+ @_raw_data['message'] = Message.new(@_raw_data['message'])
316
+ end
317
+ end
318
+
319
+ def from_user?
320
+ !!from
321
+ end
322
+
323
+ def message?
324
+ !!message
325
+ end
326
+
327
+ def inline_message?
328
+ !!inline_message_id
175
329
  end
176
330
  end
177
331
 
178
332
  class Update < BaseType
179
- attr_reader :update_id, :message, :callback_query
333
+ COMMON_FIELDS = %w[update_id message edited_message channel_post
334
+ edited_channel_post inline_query chosen_inline_result
335
+ callback_query shipping_query pre_checkout_query
336
+ poll poll_answer my_chat_member chat_member
337
+ chat_join_request].freeze
180
338
 
181
339
  def initialize(data)
182
340
  super(data)
183
341
 
184
- @update_id = data['update_id']
185
- @message = Message.new(data['message']) if data['message']
186
- @callback_query = CallbackQuery.new(data['callback_query']) if data['callback_query']
342
+ COMMON_FIELDS.each do |field|
343
+ define_accessor(field.to_sym)
344
+ end
345
+
346
+ convert_update_objects
347
+ end
348
+
349
+ def type
350
+ return :message if message
351
+ return :edited_message if edited_message
352
+ return :channel_post if channel_post
353
+ return :edited_channel_post if edited_channel_post
354
+ return :inline_query if inline_query
355
+ return :chosen_inline_result if chosen_inline_result
356
+ return :callback_query if callback_query
357
+ return :shipping_query if shipping_query
358
+ return :pre_checkout_query if pre_checkout_query
359
+ return :poll if poll
360
+ return :poll_answer if poll_answer
361
+ return :my_chat_member if my_chat_member
362
+ return :chat_member if chat_member
363
+ return :chat_join_request if chat_join_request
364
+ :unknown
365
+ end
366
+
367
+ def from
368
+ case type
369
+ when :message, :edited_message
370
+ message.from
371
+ when :channel_post, :edited_channel_post
372
+ channel_post.from
373
+ when :inline_query
374
+ inline_query.from
375
+ when :chosen_inline_result
376
+ chosen_inline_result.from
377
+ when :callback_query
378
+ callback_query.from
379
+ when :shipping_query
380
+ shipping_query.from
381
+ when :pre_checkout_query
382
+ pre_checkout_query.from
383
+ when :my_chat_member, :chat_member
384
+ my_chat_member&.from || chat_member&.from
385
+ when :chat_join_request
386
+ chat_join_request.from
387
+ else
388
+ nil
389
+ end
390
+ end
391
+
392
+ private
393
+
394
+ def convert_update_objects
395
+ @_raw_data['message'] = Message.new(@_raw_data['message']) if @_raw_data['message'] && !@_raw_data['message'].is_a?(Message)
396
+ @_raw_data['edited_message'] = Message.new(@_raw_data['edited_message']) if @_raw_data['edited_message'] && !@_raw_data['edited_message'].is_a?(Message)
397
+ @_raw_data['channel_post'] = Message.new(@_raw_data['channel_post']) if @_raw_data['channel_post'] && !@_raw_data['channel_post'].is_a?(Message)
398
+ @_raw_data['edited_channel_post'] = Message.new(@_raw_data['edited_channel_post']) if @_raw_data['edited_channel_post'] && !@_raw_data['edited_channel_post'].is_a?(Message)
399
+
400
+ if @_raw_data['callback_query'] && !@_raw_data['callback_query'].is_a?(CallbackQuery)
401
+ @_raw_data['callback_query'] = CallbackQuery.new(@_raw_data['callback_query'])
402
+ end
187
403
  end
188
404
  end
189
405
  end