telegram-bot-ruby 2.4.0 → 2.5.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -0
  3. data/CHANGELOG.md +7 -0
  4. data/lib/telegram/bot/api/endpoints.rb +102 -62
  5. data/lib/telegram/bot/types/accepted_gift_types.rb +14 -0
  6. data/lib/telegram/bot/types/business_bot_rights.rb +24 -0
  7. data/lib/telegram/bot/types/business_connection.rb +1 -1
  8. data/lib/telegram/bot/types/chat.rb +1 -0
  9. data/lib/telegram/bot/types/chat_administrator_rights.rb +1 -0
  10. data/lib/telegram/bot/types/chat_full_info.rb +3 -1
  11. data/lib/telegram/bot/types/chat_member_administrator.rb +1 -0
  12. data/lib/telegram/bot/types/checklist.rb +15 -0
  13. data/lib/telegram/bot/types/checklist_task.rb +15 -0
  14. data/lib/telegram/bot/types/checklist_tasks_added.rb +12 -0
  15. data/lib/telegram/bot/types/checklist_tasks_done.rb +13 -0
  16. data/lib/telegram/bot/types/direct_message_price_changed.rb +12 -0
  17. data/lib/telegram/bot/types/direct_messages_topic.rb +12 -0
  18. data/lib/telegram/bot/types/external_reply_info.rb +1 -0
  19. data/lib/telegram/bot/types/gift.rb +1 -0
  20. data/lib/telegram/bot/types/gift_info.rb +18 -0
  21. data/lib/telegram/bot/types/input_checklist.rb +16 -0
  22. data/lib/telegram/bot/types/input_checklist_task.rb +14 -0
  23. data/lib/telegram/bot/types/input_file.rb +10 -0
  24. data/lib/telegram/bot/types/input_profile_photo.rb +15 -0
  25. data/lib/telegram/bot/types/input_profile_photo_animated.rb +13 -0
  26. data/lib/telegram/bot/types/input_profile_photo_static.rb +12 -0
  27. data/lib/telegram/bot/types/input_story_content.rb +15 -0
  28. data/lib/telegram/bot/types/input_story_content_photo.rb +12 -0
  29. data/lib/telegram/bot/types/input_story_content_video.rb +15 -0
  30. data/lib/telegram/bot/types/location_address.rb +14 -0
  31. data/lib/telegram/bot/types/message.rb +17 -0
  32. data/lib/telegram/bot/types/owned_gift.rb +15 -0
  33. data/lib/telegram/bot/types/owned_gift_regular.rb +23 -0
  34. data/lib/telegram/bot/types/owned_gift_unique.rb +19 -0
  35. data/lib/telegram/bot/types/owned_gifts.rb +13 -0
  36. data/lib/telegram/bot/types/paid_message_price_changed.rb +11 -0
  37. data/lib/telegram/bot/types/reply_parameters.rb +1 -0
  38. data/lib/telegram/bot/types/star_amount.rb +12 -0
  39. data/lib/telegram/bot/types/story_area.rb +12 -0
  40. data/lib/telegram/bot/types/story_area_position.rb +16 -0
  41. data/lib/telegram/bot/types/story_area_type.rb +18 -0
  42. data/lib/telegram/bot/types/story_area_type_link.rb +12 -0
  43. data/lib/telegram/bot/types/story_area_type_location.rb +14 -0
  44. data/lib/telegram/bot/types/story_area_type_suggested_reaction.rb +14 -0
  45. data/lib/telegram/bot/types/story_area_type_unique_gift.rb +12 -0
  46. data/lib/telegram/bot/types/story_area_type_weather.rb +14 -0
  47. data/lib/telegram/bot/types/suggested_post_approval_failed.rb +12 -0
  48. data/lib/telegram/bot/types/suggested_post_approved.rb +13 -0
  49. data/lib/telegram/bot/types/suggested_post_declined.rb +12 -0
  50. data/lib/telegram/bot/types/suggested_post_info.rb +13 -0
  51. data/lib/telegram/bot/types/suggested_post_paid.rb +14 -0
  52. data/lib/telegram/bot/types/suggested_post_parameters.rb +12 -0
  53. data/lib/telegram/bot/types/suggested_post_price.rb +12 -0
  54. data/lib/telegram/bot/types/suggested_post_refunded.rb +12 -0
  55. data/lib/telegram/bot/types/transaction_partner_user.rb +2 -0
  56. data/lib/telegram/bot/types/unique_gift.rb +17 -0
  57. data/lib/telegram/bot/types/unique_gift_backdrop.rb +13 -0
  58. data/lib/telegram/bot/types/unique_gift_backdrop_colors.rb +14 -0
  59. data/lib/telegram/bot/types/unique_gift_info.rb +16 -0
  60. data/lib/telegram/bot/types/unique_gift_model.rb +13 -0
  61. data/lib/telegram/bot/types/unique_gift_symbol.rb +13 -0
  62. data/lib/telegram/bot/version.rb +1 -1
  63. data/rakelib/builders/endpoints_builder.rb +51 -0
  64. data/rakelib/builders/type_builder.rb +140 -0
  65. data/rakelib/parse.rake +31 -0
  66. data/rakelib/parsers/methods_parser.rb +115 -0
  67. data/rakelib/parsers/types_parser.rb +273 -0
  68. data/rakelib/rebuild.rake +30 -0
  69. data/rakelib/templates/endpoints.erb +13 -0
  70. metadata +57 -9
  71. data/rakelib/parse_schema.rake +0 -73
  72. data/rakelib/rebuild_types.rake +0 -90
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module Builders
6
+ class TypeBuilder
7
+ DRY_TYPES = %w[string integer float decimal array hash symbol boolean date date_time time range].freeze
8
+
9
+ attr_reader :name, :attributes, :templates_dir
10
+
11
+ def initialize(name, attributes, templates_dir:)
12
+ @name = name
13
+ @attributes = deep_dup(attributes)
14
+ @templates_dir = templates_dir
15
+ end
16
+
17
+ def build
18
+ if empty_type?
19
+ build_empty_type
20
+ else
21
+ build_full_type
22
+ end
23
+ end
24
+
25
+ def self.underscore(camel_cased_word)
26
+ camel_cased_word.to_s.gsub('::', '/')
27
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
28
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
29
+ .tr('-', '_')
30
+ .downcase
31
+ end
32
+
33
+ private
34
+
35
+ def empty_type?
36
+ attributes[:type].is_a?(Array)
37
+ end
38
+
39
+ def build_empty_type
40
+ attrs = attributes[:type].join(" |\n ")
41
+ render_template('empty_type.erb', name: name, attributes: attrs)
42
+ end
43
+
44
+ def build_full_type
45
+ process_attributes!
46
+ render_template('type.erb', name: name, attributes: attributes)
47
+ end
48
+
49
+ def process_attributes!
50
+ attributes.each_pair do |attr_name, properties|
51
+ process_attribute!(attr_name, properties)
52
+ end
53
+ end
54
+
55
+ def process_attribute!(attr_name, properties)
56
+ process_type!(attr_name, properties)
57
+ process_items!(attr_name, properties)
58
+
59
+ original_type = properties[:type]
60
+ apply_required!(attr_name, properties, original_type)
61
+ apply_min_max!(attr_name, properties)
62
+ apply_default!(attr_name, properties, original_type)
63
+
64
+ normalize_boolean_type!(attr_name)
65
+ end
66
+
67
+ def normalize_boolean_type!(attr_name)
68
+ attributes[attr_name][:type] = 'Types::True' if attributes[attr_name][:type] == 'Types::Boolean.default(true)'
69
+ attributes[attr_name][:type] = attributes[attr_name][:type].gsub('Types::Boolean', 'Types::Bool')
70
+ end
71
+
72
+ def process_type!(attr_name, properties)
73
+ attributes[attr_name][:type] = if properties[:type].is_a?(Array)
74
+ properties[:type].map { |t| add_module_types(t) }.join(' | ')
75
+ else
76
+ add_module_types(properties[:type])
77
+ end
78
+ end
79
+
80
+ def process_items!(attr_name, properties)
81
+ if properties[:items].is_a?(String)
82
+ attributes[attr_name][:type] += ".of(#{add_module_types(properties[:items])})"
83
+ elsif properties[:items] && properties[:items][:type] == 'array'
84
+ attributes[attr_name][:type] += ".of(Types::Array.of(#{properties[:items][:items]}))"
85
+ end
86
+ end
87
+
88
+ def apply_required!(attr_name, properties, original_type)
89
+ return unless properties[:required_value]
90
+
91
+ attributes[attr_name][:type] += ".constrained(eql: #{typecast(original_type, properties[:required_value])})"
92
+ end
93
+
94
+ def apply_min_max!(attr_name, properties)
95
+ return unless properties[:min_size] || properties[:max_size]
96
+
97
+ constrain = '.constrained(minmax)'
98
+ constrain = properties[:min_size] ? constrain.gsub('min', "min_size: #{properties[:min_size]}, ") : ''
99
+ constrain = constrain.gsub('max', "max_size: #{properties[:max_size]}") if properties[:max_size]
100
+ attributes[attr_name][:type] += constrain
101
+ end
102
+
103
+ def apply_default!(attr_name, properties, original_type)
104
+ return if properties[:default].nil?
105
+
106
+ attributes[attr_name][:type] += ".default(#{typecast(original_type, properties[:default])})"
107
+ end
108
+
109
+ def add_module_types(type)
110
+ return 'Types::Float' if type == 'number'
111
+
112
+ DRY_TYPES.include?(type) ? "Types::#{type.capitalize}" : type
113
+ end
114
+
115
+ def typecast(type, obj)
116
+ type == 'Types::String' ? "'#{obj}'" : obj
117
+ end
118
+
119
+ def render_template(template_name, vars)
120
+ template_path = File.join(templates_dir, template_name)
121
+ template = ERB.new(File.read(template_path))
122
+
123
+ # Create binding with the variables
124
+ b = binding
125
+ vars.each { |k, v| b.local_variable_set(k, v) }
126
+
127
+ template.result(b).gsub(" \n", '')
128
+ end
129
+
130
+ def deep_dup(hash)
131
+ hash.transform_values do |v|
132
+ case v
133
+ when Hash then deep_dup(v)
134
+ when Array then v.dup
135
+ else v
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'parsers/types_parser'
5
+ require_relative 'parsers/methods_parser'
6
+
7
+ namespace :parse do
8
+ desc 'Parse types from Telegram Bot API HTML documentation'
9
+ task :types do
10
+ puts 'Parsing types from Telegram Bot API...'
11
+
12
+ result = Parsers::TypesParser.new.parse
13
+
14
+ puts "Found #{result.keys.count} types"
15
+
16
+ File.write "#{__dir__}/../data/types.json", JSON.pretty_generate(result)
17
+ puts 'Written to data/types.json'
18
+ end
19
+
20
+ desc 'Parse methods from Telegram Bot API HTML documentation'
21
+ task :methods do
22
+ puts 'Parsing methods from Telegram Bot API...'
23
+
24
+ result = Parsers::MethodsParser.new.parse
25
+
26
+ puts "Found #{result.keys.count} methods"
27
+
28
+ File.write "#{__dir__}/../data/methods.json", JSON.pretty_generate(result)
29
+ puts 'Written to data/methods.json'
30
+ end
31
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'net/http'
5
+
6
+ module Parsers
7
+ class MethodsParser
8
+ API_URL = 'https://core.telegram.org/bots/api'
9
+
10
+ def parse
11
+ doc = fetch_document
12
+ result = {}
13
+
14
+ method_headers(doc).each do |header|
15
+ method_name = extract_method_name(header)
16
+ next unless method_name
17
+
18
+ return_type = extract_return_type(header)
19
+ result[method_name] = return_type if return_type
20
+ end
21
+
22
+ result
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_document
28
+ uri = URI.parse(API_URL)
29
+ response = Net::HTTP.get(uri)
30
+ Nokogiri::HTML(response)
31
+ end
32
+
33
+ def method_headers(doc)
34
+ doc.css('h4')
35
+ end
36
+
37
+ def extract_method_name(header)
38
+ name = header.text.strip
39
+ # Methods start with lowercase letter (camelCase)
40
+ # Types start with uppercase (PascalCase) - skip those
41
+ return nil unless name.match?(/\A[a-z][a-zA-Z0-9]*\z/)
42
+
43
+ name
44
+ end
45
+
46
+ def extract_return_type(header)
47
+ # Find description paragraphs after the header
48
+ sibling = header.next_element
49
+
50
+ while sibling
51
+ break if sibling.name == 'h4' # Next section
52
+
53
+ if sibling.name == 'p'
54
+ return_type = parse_return_statement(sibling)
55
+ return return_type if return_type
56
+ end
57
+
58
+ sibling = sibling.next_element
59
+ end
60
+
61
+ nil
62
+ end
63
+
64
+ def parse_return_statement(paragraph)
65
+ html = paragraph.inner_html
66
+
67
+ # Pattern: Returns an Array of <a>Type</a>
68
+ if (match = html.match(%r{Returns an Array of <a[^>]*>([^<]+)</a>}i))
69
+ return "Array<#{match[1]}>"
70
+ end
71
+
72
+ # Pattern: On success, an array of <a>Type</a> (lowercase)
73
+ if (match = html.match(%r{On success,? an array of <a[^>]*>([^<]+)</a>}i))
74
+ return "Array<#{match[1]}>"
75
+ end
76
+
77
+ # Pattern: Returns a <a>Type</a> or Returns the <a>Type</a>
78
+ if (match = html.match(%r{Returns (?:a |the )?.*?<a[^>]*>([^<]+)</a>}i))
79
+ return match[1]
80
+ end
81
+
82
+ # Pattern: Returns basic information ... in form of a <a>Type</a>
83
+ if (match = html.match(%r{in form of a <a[^>]*>([^<]+)</a>}i))
84
+ return match[1]
85
+ end
86
+
87
+ # Pattern: <a>Type</a> is returned, otherwise True is returned (union type)
88
+ # e.g., "the edited Message is returned, otherwise True is returned"
89
+ if (match = html.match(%r{<a[^>]*>([^<]+)</a> is returned,? otherwise <em>True</em> is returned}i))
90
+ return "#{match[1]} | Boolean"
91
+ end
92
+
93
+ # Pattern: Returns <em>True</em> or On success, <em>True</em> is returned
94
+ return 'Boolean' if html.match?(%r{<em>True</em>(?:\s+(?:is|on)|\s*\.)}i)
95
+
96
+ # Pattern: On success, a <a>Type</a> object is returned
97
+ if (match = html.match(%r{On success,? a <a[^>]*>([^<]+)</a> object is returned}i))
98
+ return match[1]
99
+ end
100
+
101
+ # Pattern: On success, the <a>Type</a> is returned / the edited/sent/stopped <a>X</a> is returned
102
+ if (match = html.match(%r{the (?:sent |edited |stopped )?<a[^>]*>([^<]+)</a> is returned}i))
103
+ return match[1]
104
+ end
105
+
106
+ # Pattern: Returns <em>Int</em> or Integer
107
+ return 'Integer' if html.match?(%r{Returns <em>Int</em>}i)
108
+
109
+ # Pattern: Returns ... as <em>String</em>
110
+ return 'String' if html.match?(%r{as <em>String</em>}i)
111
+
112
+ nil
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'net/http'
5
+
6
+ module Parsers
7
+ class TypesParser
8
+ API_URL = 'https://core.telegram.org/bots/api'
9
+
10
+ PRIMITIVE_TYPES = {
11
+ 'String' => 'string',
12
+ 'Integer' => 'integer',
13
+ 'Boolean' => 'boolean',
14
+ 'Float' => 'number',
15
+ 'True' => 'boolean'
16
+ }.freeze
17
+
18
+ def parse
19
+ doc = fetch_document
20
+ result = {}
21
+
22
+ type_headers(doc).each do |header|
23
+ type_name = extract_type_name(header)
24
+ next unless type_name
25
+
26
+ type_data = parse_type(header, type_name)
27
+ result[type_name] = type_data if type_data
28
+ end
29
+
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ def fetch_document
36
+ uri = URI.parse(API_URL)
37
+ response = Net::HTTP.get(uri)
38
+ Nokogiri::HTML(response)
39
+ end
40
+
41
+ def type_headers(doc)
42
+ doc.css('h4')
43
+ end
44
+
45
+ def extract_type_name(header)
46
+ name = header.text.strip
47
+ # Types start with uppercase letter (CapitalCase)
48
+ # Methods start with lowercase (camelCase) - skip those
49
+ return nil unless name.match?(/\A[A-Z][a-zA-Z0-9]*\z/)
50
+
51
+ name
52
+ end
53
+
54
+ def parse_type(header, _type_name)
55
+ # Check if this is a union type (list of types without a table)
56
+ next_sibling = find_next_significant_sibling(header)
57
+
58
+ if union_type?(next_sibling)
59
+ parse_union_type(next_sibling)
60
+ else
61
+ parse_table_type(header)
62
+ end
63
+ end
64
+
65
+ def find_next_significant_sibling(header)
66
+ sibling = header.next_element
67
+ # Skip description paragraphs
68
+ sibling = sibling.next_element while sibling && sibling.name == 'p'
69
+ sibling
70
+ end
71
+
72
+ def union_type?(element)
73
+ element&.name == 'ul'
74
+ end
75
+
76
+ def parse_union_type(ul_element)
77
+ types = ul_element.css('li a').map { |a| a.text.strip }
78
+ { 'type' => types }
79
+ end
80
+
81
+ def parse_table_type(header)
82
+ table = find_attribute_table(header)
83
+ return {} unless table
84
+
85
+ attributes = {}
86
+ table.css('tbody tr').each do |row|
87
+ cells = row.css('td')
88
+ next unless cells.length >= 3
89
+
90
+ field_name = cells[0].text.strip
91
+ type_info = cells[1]
92
+ description_cell = cells[2]
93
+
94
+ attributes[field_name] = parse_attribute(type_info, description_cell)
95
+ end
96
+
97
+ attributes
98
+ end
99
+
100
+ def find_attribute_table(header)
101
+ sibling = header.next_element
102
+ while sibling
103
+ return sibling if sibling.name == 'table'
104
+ # Stop if we hit another h4 (next type/method)
105
+ break if sibling.name == 'h4'
106
+
107
+ sibling = sibling.next_element
108
+ end
109
+ nil
110
+ end
111
+
112
+ def parse_attribute(type_cell, description_cell)
113
+ attribute = {}
114
+ raw_type = type_cell.text.strip
115
+ description = description_cell.text.strip
116
+ description_html = description_cell.inner_html
117
+
118
+ # Parse type
119
+ type_value = parse_type_value(type_cell)
120
+ if type_value.is_a?(Hash)
121
+ attribute.merge!(type_value)
122
+ else
123
+ attribute['type'] = type_value
124
+ end
125
+
126
+ # Parse required (absence of "Optional" at start of description)
127
+ attribute['required'] = true unless description.start_with?('Optional')
128
+
129
+ # Parse required_value (always "X" or must be <em>X</em>)
130
+ required_value = extract_required_value(description, description_html)
131
+ if required_value
132
+ attribute['required_value'] = required_value
133
+ attribute['default'] = required_value
134
+ end
135
+
136
+ # Parse size constraints (N-M characters or must be between)
137
+ min_size, max_size = extract_size_constraints(description)
138
+ attribute['min_size'] = min_size if min_size
139
+ attribute['max_size'] = max_size if max_size
140
+
141
+ # Parse default value (Defaults to X)
142
+ # If HTML type is 'True' (not 'Boolean'), it means the field only exists when true
143
+ if raw_type == 'True' && !attribute.key?('default')
144
+ attribute['default'] = true
145
+ elsif !attribute.key?('default')
146
+ default = extract_default_value(description, description_html, attribute['type'])
147
+ attribute['default'] = default unless default.nil?
148
+ end
149
+
150
+ # Clean up: remove required if false
151
+ attribute.delete('required') unless attribute['required']
152
+
153
+ attribute
154
+ end
155
+
156
+ def parse_type_value(type_cell)
157
+ text = type_cell.text.strip
158
+ links = type_cell.css('a')
159
+
160
+ # Check for "Array of X"
161
+ if text.start_with?('Array of')
162
+ items_type = parse_array_items(type_cell, text)
163
+ return { 'type' => 'array', 'items' => items_type }
164
+ end
165
+
166
+ # Check for union type "X or Y"
167
+ if text.include?(' or ')
168
+ types = parse_union_types(type_cell, text)
169
+ return types.length == 1 ? normalize_type(types.first) : types.map { |t| normalize_type(t) }
170
+ end
171
+
172
+ # Single type
173
+ if links.any?
174
+ normalize_type(links.first.text.strip)
175
+ else
176
+ normalize_type(text)
177
+ end
178
+ end
179
+
180
+ def parse_array_items(type_cell, text)
181
+ # Handle "Array of Array of X"
182
+ if text.include?('Array of Array of')
183
+ inner_type = type_cell.css('a').last&.text&.strip || text.split('Array of Array of').last.strip
184
+ return { 'type' => 'array', 'items' => normalize_type(inner_type) }
185
+ end
186
+
187
+ # Regular "Array of X"
188
+ link = type_cell.css('a').first
189
+ if link
190
+ normalize_type(link.text.strip)
191
+ else
192
+ # Primitive array like "Array of String"
193
+ items_text = text.sub('Array of ', '').strip
194
+ normalize_type(items_text)
195
+ end
196
+ end
197
+
198
+ def parse_union_types(type_cell, text)
199
+ links = type_cell.css('a')
200
+ if links.any?
201
+ links.map { |l| l.text.strip }
202
+ else
203
+ text.split(' or ').map(&:strip)
204
+ end
205
+ end
206
+
207
+ def normalize_type(type)
208
+ PRIMITIVE_TYPES[type] || type
209
+ end
210
+
211
+ def extract_required_value(description, description_html)
212
+ # Pattern: always "X" or always "X" (smart quotes)
213
+ match = description.match(/always ["\u201c]([^"\u201d]+)["\u201d]/i)
214
+ return match[1].delete('\\') if match
215
+
216
+ # Pattern: must be <em>X</em> (check inner HTML)
217
+ match = description_html.match(%r{must be <em>([^<]+)</em>}i)
218
+ return match[1] if match
219
+
220
+ nil
221
+ end
222
+
223
+ def extract_size_constraints(description)
224
+ min_size = nil
225
+ max_size = nil
226
+
227
+ # Pattern: N-M characters (covers 0-N and 1-N cases)
228
+ if (match = description.match(/(\d+)-(\d+) characters/))
229
+ min_val = match[1].to_i
230
+ max_size = match[2].to_i
231
+ min_size = min_val if min_val.positive?
232
+ # Pattern: must be between N and M
233
+ elsif (match = description.match(/must be between (\d+) and (\d+)/))
234
+ min_size = match[1].to_i
235
+ max_size = match[2].to_i
236
+ end
237
+
238
+ [min_size, max_size]
239
+ end
240
+
241
+ def extract_default_value(description, description_html, type)
242
+ # Pattern: Defaults to "X" (with smart quotes U+201C/U+201D)
243
+ if (match = description.match(/Defaults to \u201c([^\u201d]+)\u201d/i))
244
+ return cast_default_value(match[1], type)
245
+ end
246
+
247
+ # Pattern: Defaults to <em>X</em> (check inner HTML for em-wrapped values)
248
+ if (match = description_html.match(%r{Defaults to <em>([^<]+)</em>}i))
249
+ return cast_default_value(match[1], type)
250
+ end
251
+
252
+ # Pattern: Defaults to <number> (plain numeric values)
253
+ if (match = description.match(/Defaults to (\d+)/i))
254
+ return cast_default_value(match[1], type)
255
+ end
256
+
257
+ nil
258
+ end
259
+
260
+ def cast_default_value(value, type)
261
+ case type
262
+ when 'integer'
263
+ value.to_i
264
+ when 'boolean'
265
+ value.downcase == 'true'
266
+ when 'number'
267
+ value.to_f
268
+ else
269
+ value
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'builders/type_builder'
5
+ require_relative 'builders/endpoints_builder'
6
+
7
+ namespace :rebuild do
8
+ desc 'Rebuild types from type_attributes.json'
9
+ task :types do
10
+ types = JSON.parse(File.read("#{__dir__}/../data/types.json"), symbolize_names: true)
11
+ templates_dir = "#{__dir__}/templates"
12
+
13
+ types.each_pair do |name, attributes|
14
+ builder = Builders::TypeBuilder.new(name.to_s, attributes, templates_dir: templates_dir)
15
+ output_path = "#{__dir__}/../lib/telegram/bot/types/#{Builders::TypeBuilder.underscore(name)}.rb"
16
+
17
+ File.write(output_path, builder.build)
18
+ end
19
+ end
20
+
21
+ desc 'Rebuild API endpoints from method_return_types.json'
22
+ task :methods do
23
+ methods = JSON.parse(File.read("#{__dir__}/../data/methods.json"))
24
+ templates_dir = "#{__dir__}/templates"
25
+
26
+ builder = Builders::EndpointsBuilder.new(methods, templates_dir: templates_dir)
27
+
28
+ File.write "#{__dir__}/../lib/telegram/bot/api/endpoints.rb", builder.build
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Telegram
4
+ module Bot
5
+ class Api
6
+ ENDPOINTS = {
7
+ <%- methods.each_with_index do |(name, return_type), index| -%>
8
+ '<%= name %>' => <%= return_type %><%= index == methods.size - 1 ? '' : ',' %>
9
+ <%- end -%>
10
+ }.freeze
11
+ end
12
+ end
13
+ end