domainnameapi 1.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.
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DomainNameApi
4
+ # Public facade, the Ruby counterpart of php-dna's DomainNameAPI_PHPLibrary.
5
+ #
6
+ # dna = DomainNameApi::Client.new('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'your-api-key')
7
+ # dna.get_details('example.com')
8
+ # # => { 'data' => { 'DomainName' => 'example.com', ... }, 'result' => 'OK' }
9
+ #
10
+ # Every network method returns php-dna's result structure (Hashes with
11
+ # String keys) and never raises for API or transport errors: failures come
12
+ # back as { 'result' => 'ERROR', 'error' => { 'Code', 'Message', 'Details' } }
13
+ # (get_current_balance uses its own OperationResult envelope).
14
+ class Client
15
+ attr_reader :rest
16
+
17
+ # @param reseller_id [String] Reseller ID (UUID) from the panel's API settings
18
+ # @param api_key [String] API key from the panel's API settings
19
+ # @param test_mode [Boolean, String] true (or "true"/"1"/"on"/"yes") for the OTE test gateway
20
+ # @param transport [#call, nil] custom HTTP transport (see NetHttpTransport)
21
+ # @param sentry_dsn [String, nil] override the telemetry DSN ('' disables telemetry)
22
+ # @param zone_min_interval [Numeric, nil] seconds between /domains/zones calls (default 1.0)
23
+ def initialize(reseller_id = '', api_key = '', test_mode = false, transport: nil, sentry_dsn: nil,
24
+ zone_min_interval: nil)
25
+ reseller_id = Php.trim(reseller_id)
26
+ api_key = Php.trim(api_key)
27
+ test_mode = test_mode.is_a?(String) ? Php.filter_bool(test_mode) : Php.truthy?(test_mode)
28
+ @rest = Rest.new(reseller_id, api_key, test_mode, transport: transport, sentry_dsn: sentry_dsn,
29
+ zone_min_interval: zone_min_interval)
30
+ end
31
+
32
+ # Account
33
+
34
+ def get_reseller_details
35
+ @rest.get_reseller_details
36
+ end
37
+
38
+ def get_current_balance(currency_id = 'USD')
39
+ @rest.get_current_balance(currency_id)
40
+ end
41
+
42
+ # Search and lists
43
+
44
+ def check_availability(domains, extensions, period, command = 'create')
45
+ @rest.check_availability(domains, extensions, period, command)
46
+ end
47
+
48
+ def get_list(extra_parameters = {})
49
+ @rest.get_list(extra_parameters)
50
+ end
51
+
52
+ def get_tld_list(count = 20)
53
+ @rest.get_tld_list(count)
54
+ end
55
+
56
+ # Domain
57
+
58
+ def get_details(domain_name)
59
+ @rest.get_details(domain_name)
60
+ end
61
+
62
+ def sync_from_registry(domain_name)
63
+ @rest.sync_from_registry(domain_name)
64
+ end
65
+
66
+ def get_contacts(domain_name)
67
+ @rest.get_contacts(domain_name)
68
+ end
69
+
70
+ def save_contacts(domain_name, contacts)
71
+ @rest.save_contacts(domain_name, contacts)
72
+ end
73
+
74
+ def modify_name_server(domain_name, name_servers)
75
+ @rest.modify_name_server(domain_name, name_servers)
76
+ end
77
+
78
+ def enable_theft_protection_lock(domain_name)
79
+ @rest.enable_theft_protection_lock(domain_name)
80
+ end
81
+
82
+ def disable_theft_protection_lock(domain_name)
83
+ @rest.disable_theft_protection_lock(domain_name)
84
+ end
85
+
86
+ # `status` accepts true/false and 1/"1"/"on"/"true"/"yes"; `reason` is
87
+ # accepted for compatibility and not sent (the gateway has no such field).
88
+ def modify_privacy_protection_status(domain_name, status, reason = 'Owner request')
89
+ @rest.modify_privacy_protection_status(domain_name, status, reason)
90
+ end
91
+
92
+ def renew(domain_name, period)
93
+ @rest.renew(domain_name, period)
94
+ end
95
+
96
+ def register_with_contact_info(domain_name, period, contacts, name_servers = [], epp_lock = true,
97
+ privacy_lock = false, additional_attributes = {})
98
+ @rest.register_with_contact_info(domain_name, period, contacts, name_servers, epp_lock, privacy_lock,
99
+ additional_attributes)
100
+ end
101
+
102
+ # Child nameservers
103
+
104
+ def add_child_name_server(domain_name, name_server, ip_address)
105
+ @rest.add_child_name_server(domain_name, name_server, ip_address)
106
+ end
107
+
108
+ def delete_child_name_server(domain_name, name_server)
109
+ @rest.delete_child_name_server(domain_name, name_server)
110
+ end
111
+
112
+ def modify_child_name_server(domain_name, name_server, ip_address)
113
+ @rest.modify_child_name_server(domain_name, name_server, ip_address)
114
+ end
115
+
116
+ # Transfers
117
+
118
+ # Contacts are never sent with a transfer: the registrar handles them,
119
+ # so the request always carries an empty contact list.
120
+ def transfer(domain_name, epp_code, period)
121
+ @rest.transfer(domain_name, epp_code, period)
122
+ end
123
+
124
+ def cancel_transfer(domain_name)
125
+ @rest.cancel_transfer(domain_name)
126
+ end
127
+
128
+ def approve_transfer(domain_name)
129
+ @rest.approve_transfer(domain_name)
130
+ end
131
+
132
+ def reject_transfer(domain_name)
133
+ @rest.reject_transfer(domain_name)
134
+ end
135
+
136
+ def check_transfer(domain_name, auth_code)
137
+ @rest.check_transfer(domain_name, auth_code)
138
+ end
139
+
140
+ # DNS zone
141
+
142
+ def get_resource_records(domain_name)
143
+ @rest.get_resource_records(domain_name)
144
+ end
145
+
146
+ def add_resource_record(domain_name, name, type, content, ttl = 3600, apply = true)
147
+ @rest.add_resource_record(domain_name, name, type, content, ttl, apply)
148
+ end
149
+
150
+ def edit_resource_record(domain_name, record_name, name, type, content, ttl = 3600, apply = true)
151
+ @rest.edit_resource_record(domain_name, record_name, name, type, content, ttl, apply)
152
+ end
153
+
154
+ def delete_resource_record(domain_name, name, record, record_type, apply = true)
155
+ @rest.delete_resource_record(domain_name, name, record, record_type, apply)
156
+ end
157
+
158
+ def apply_resource_records(domain_name)
159
+ @rest.apply_resource_records(domain_name)
160
+ end
161
+
162
+ def set_resource_record_set(domain_name, name, type, contents, ttl = 3600, apply = true)
163
+ @rest.set_resource_record_set(domain_name, name, type, contents, ttl, apply)
164
+ end
165
+
166
+ def delete_resource_record_set(domain_name, name, type, apply = true, known_value = '')
167
+ @rest.delete_resource_record_set(domain_name, name, type, apply, known_value)
168
+ end
169
+
170
+ def add_resource_record_value(domain_name, name, type, value, ttl = nil, apply = true)
171
+ @rest.add_resource_record_value(domain_name, name, type, value, ttl, apply)
172
+ end
173
+
174
+ def remove_resource_record_value(domain_name, name, type, value, apply = true)
175
+ @rest.remove_resource_record_value(domain_name, name, type, value, apply)
176
+ end
177
+
178
+ def replace_resource_record_value(domain_name, name, type, old_value, new_value, ttl = nil, apply = true)
179
+ @rest.replace_resource_record_value(domain_name, name, type, old_value, new_value, ttl, apply)
180
+ end
181
+
182
+ # Forwarding
183
+
184
+ def get_forwarding(domain_name)
185
+ @rest.get_forwarding(domain_name)
186
+ end
187
+
188
+ def set_forwarding(domain_name, redirect_address, forward_type = 'Standard')
189
+ @rest.set_forwarding(domain_name, redirect_address, forward_type)
190
+ end
191
+
192
+ def delete_forwarding(domain_name)
193
+ @rest.delete_forwarding(domain_name)
194
+ end
195
+
196
+ # Utilities (no network)
197
+
198
+ def is_tr_tld(domain)
199
+ @rest.is_tr_tld(domain)
200
+ end
201
+ alias tr_tld? is_tr_tld
202
+
203
+ def validate_contact(contact)
204
+ @rest.validate_contact(contact)
205
+ end
206
+
207
+ # Diagnostics
208
+
209
+ def get_request_data
210
+ @rest.get_request_data
211
+ end
212
+
213
+ def set_request_data(request)
214
+ @rest.set_request_data(request)
215
+ self
216
+ end
217
+
218
+ def get_response_data
219
+ @rest.get_response_data
220
+ end
221
+
222
+ def set_response_data(response)
223
+ @rest.set_response_data(response)
224
+ self
225
+ end
226
+
227
+ def get_response_headers
228
+ @rest.get_response_headers
229
+ end
230
+
231
+ def set_response_headers(headers)
232
+ @rest.set_response_headers(headers)
233
+ self
234
+ end
235
+
236
+ def get_parsed_response_data
237
+ @rest.get_parsed_response_data
238
+ end
239
+
240
+ def set_parsed_response_data(response)
241
+ @rest.set_parsed_response_data(response)
242
+ self
243
+ end
244
+
245
+ def get_last_function
246
+ @rest.get_last_function
247
+ end
248
+
249
+ def set_last_function(function)
250
+ @rest.set_last_function(function)
251
+ self
252
+ end
253
+
254
+ def get_service_url
255
+ @rest.get_service_url
256
+ end
257
+
258
+ def set_service_url(url)
259
+ @rest.set_service_url(url)
260
+ nil
261
+ end
262
+
263
+ # Settings
264
+
265
+ def set_error_reporting_enabled(enabled)
266
+ @rest.set_error_reporting_enabled(enabled)
267
+ end
268
+
269
+ def set_telemetry_minimal(minimal)
270
+ @rest.set_telemetry_minimal(minimal)
271
+ end
272
+
273
+ def set_ssl_verification(enabled)
274
+ @rest.set_ssl_verification(enabled)
275
+ end
276
+
277
+ def set_sentry_dsn(dsn)
278
+ @rest.set_sentry_dsn(dsn)
279
+ end
280
+
281
+ def set_telemetry_sender(sender)
282
+ @rest.set_telemetry_sender(sender)
283
+ end
284
+
285
+ def set_transport(transport)
286
+ @rest.set_transport(transport)
287
+ end
288
+
289
+ def set_zone_min_interval(seconds)
290
+ @rest.zone_min_interval = seconds.to_f
291
+ nil
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module DomainNameApi
6
+ # PHP value semantics used by php-dna (empty(), casts, number_format(),
7
+ # http_build_query(), ...). The library mirrors php-dna's results exactly,
8
+ # so these helpers reproduce what PHP does rather than what Ruby would do.
9
+ # Internal API.
10
+ module Php
11
+ module_function
12
+
13
+ TRIM_CHARS = " \t\n\r\0\x0B"
14
+ NUMERIC_RE = /\A[ \t\n\r\v\f]*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?[ \t\n\r\v\f]*\z/.freeze
15
+ LEADING_NUMERIC_RE = /\A[ \t\n\r\v\f]*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?/.freeze
16
+
17
+ # A PHP array: either a Ruby Hash (associative) or Array (list).
18
+ def array?(value)
19
+ value.is_a?(Hash) || value.is_a?(Array)
20
+ end
21
+
22
+ # PHP empty(): null, false, 0, 0.0, "", "0" and empty arrays.
23
+ def empty?(value)
24
+ case value
25
+ when nil, false then true
26
+ when Integer, Float then value.zero?
27
+ when String then value.empty? || value == '0'
28
+ when Hash, Array then value.empty?
29
+ else false
30
+ end
31
+ end
32
+
33
+ # PHP truthiness ((bool) cast).
34
+ def truthy?(value)
35
+ !empty?(value)
36
+ end
37
+
38
+ # $array[$key] with PHP's ?? semantics: nil when the container is not an
39
+ # array or the key is absent. Several keys walk nested arrays.
40
+ def get(container, *keys)
41
+ keys.each do |key|
42
+ container = fetch_one(container, key)
43
+ return nil if container.nil?
44
+ end
45
+ container
46
+ end
47
+
48
+ def fetch_one(container, key)
49
+ case container
50
+ when Hash
51
+ return container[key] if container.key?(key)
52
+
53
+ skey = key.is_a?(Symbol) ? key.to_s : key
54
+ return container[skey] if container.key?(skey)
55
+ return container[key.to_s] if key.is_a?(Integer) && container.key?(key.to_s)
56
+ return container[key.to_i] if key.is_a?(String) && key =~ /\A(0|-?[1-9]\d*)\z/ && container.key?(key.to_i)
57
+
58
+ nil
59
+ when Array
60
+ index = if key.is_a?(Integer)
61
+ key
62
+ elsif key.is_a?(String) && key =~ /\A(0|[1-9]\d*)\z/
63
+ key.to_i
64
+ end
65
+ index.nil? || index.negative? ? nil : container[index]
66
+ end
67
+ end
68
+
69
+ # PHP isset($a[$k]...): present and not null.
70
+ def isset?(container, *keys)
71
+ !get(container, *keys).nil?
72
+ end
73
+
74
+ # foreach ($array as $v): the values of a Hash or Array.
75
+ def values(value)
76
+ case value
77
+ when Hash then value.values
78
+ when Array then value
79
+ else []
80
+ end
81
+ end
82
+
83
+ # foreach ($array as $k => $v) pairs; list indexes become integer keys.
84
+ def pairs(value)
85
+ case value
86
+ when Hash then value.to_a
87
+ when Array then value.each_with_index.map { |v, i| [i, v] }
88
+ else []
89
+ end
90
+ end
91
+
92
+ # (array) cast.
93
+ def to_array(value)
94
+ case value
95
+ when nil then {}
96
+ when Hash, Array then value
97
+ else [value]
98
+ end
99
+ end
100
+
101
+ # A Hash view of a PHP array (list indexes become string keys).
102
+ def to_hash(value)
103
+ case value
104
+ when Hash then stringify_keys(value)
105
+ when Array then value.each_with_index.to_h { |v, i| [i.to_s, v] }
106
+ else {}
107
+ end
108
+ end
109
+
110
+ # Deep copy with String keys (Ruby callers may pass Symbol keys).
111
+ def stringify_keys(value)
112
+ case value
113
+ when Hash then value.each_with_object({}) { |(k, v), h| h[k.is_a?(Symbol) ? k.to_s : k] = stringify_keys(v) }
114
+ when Array then value.map { |v| stringify_keys(v) }
115
+ else value
116
+ end
117
+ end
118
+
119
+ # (string) cast.
120
+ def to_s(value)
121
+ case value
122
+ when nil, false then ''
123
+ when true then '1'
124
+ when String then value
125
+ when Float then float_to_s(value)
126
+ when Integer then value.to_s
127
+ when Symbol then value.to_s
128
+ when Hash, Array then 'Array'
129
+ else value.to_s
130
+ end
131
+ end
132
+
133
+ # (string) of a float, PHP 8 style (precision=14).
134
+ def float_to_s(value)
135
+ return 'NAN' if value.nan?
136
+ return(value.positive? ? 'INF' : '-INF') if value.infinite?
137
+ return '-0' if value.zero? && (1.0 / value).negative?
138
+ return '0' if value.zero?
139
+
140
+ str = format('%.14G', value)
141
+ if str.include?('E')
142
+ mantissa, exp = str.split('E')
143
+ mantissa = mantissa.sub(/(\.\d*?)0+\z/, '\1').sub(/\.\z/, '')
144
+ mantissa += '.0' unless mantissa.include?('.')
145
+ sign = exp.start_with?('-') ? '-' : '+'
146
+ "#{mantissa}E#{sign}#{exp.sub(/\A[+-]/, '').sub(/\A0+(?=\d)/, '')}"
147
+ else
148
+ str.include?('.') ? str.sub(/0+\z/, '').sub(/\.\z/, '') : str
149
+ end
150
+ end
151
+
152
+ # (int) cast.
153
+ def to_i(value)
154
+ case value
155
+ when nil, false then 0
156
+ when true then 1
157
+ when Integer then value
158
+ when Float then value.nan? || value.infinite? ? 0 : value.truncate
159
+ when String
160
+ m = LEADING_NUMERIC_RE.match(value)
161
+ return 0 unless m
162
+ return m[0].strip.to_i if m[2].nil? && m[3].nil? && !m[0].include?('.')
163
+
164
+ f = m[0].strip.to_f
165
+ f.nan? || f.infinite? ? 0 : f.truncate
166
+ when Hash, Array then value.empty? ? 0 : 1
167
+ else 0
168
+ end
169
+ end
170
+
171
+ # (float) cast.
172
+ def to_f(value)
173
+ case value
174
+ when nil, false then 0.0
175
+ when true then 1.0
176
+ when Integer, Float then value.to_f
177
+ when String
178
+ m = LEADING_NUMERIC_RE.match(value)
179
+ m ? m[0].strip.to_f : 0.0
180
+ when Hash, Array then value.empty? ? 0.0 : 1.0
181
+ else 0.0
182
+ end
183
+ end
184
+
185
+ # is_numeric().
186
+ def numeric?(value)
187
+ case value
188
+ when Integer, Float then true
189
+ when String then NUMERIC_RE.match?(value)
190
+ else false
191
+ end
192
+ end
193
+
194
+ # trim() with PHP's default character list (or a custom one).
195
+ def trim(value, chars = TRIM_CHARS)
196
+ rtrim(ltrim(value, chars), chars)
197
+ end
198
+
199
+ def ltrim(value, chars = TRIM_CHARS)
200
+ str = to_s(value)
201
+ i = 0
202
+ i += 1 while i < str.length && chars.include?(str[i])
203
+ str[i..] || ''
204
+ end
205
+
206
+ def rtrim(value, chars = TRIM_CHARS)
207
+ str = to_s(value)
208
+ j = str.length
209
+ j -= 1 while j.positive? && chars.include?(str[j - 1])
210
+ str[0, j] || ''
211
+ end
212
+
213
+ # strtolower()/strtoupper()/ucfirst(): ASCII only, like PHP 8.
214
+ def lower(value)
215
+ to_s(value).downcase(:ascii)
216
+ end
217
+
218
+ def upper(value)
219
+ to_s(value).upcase(:ascii)
220
+ end
221
+
222
+ def ucfirst(value)
223
+ str = to_s(value)
224
+ str.empty? ? str : str[0].upcase(:ascii) + str[1..]
225
+ end
226
+
227
+ # strcasecmp($a, $b) === 0
228
+ def casecmp_eq?(a, b)
229
+ lower(a) == lower(b)
230
+ end
231
+
232
+ # number_format($value, $decimals, '.', '')
233
+ def number_format(value, decimals)
234
+ f = to_f(value)
235
+ return f.to_s if f.nan? || f.infinite?
236
+
237
+ rounded = Rational(f.to_s.sub(/e/, 'E')).round(decimals, half: :up)
238
+ negative = rounded.negative?
239
+ rounded = rounded.abs
240
+ scaled = (rounded * (10**decimals)).to_i
241
+ int_part = scaled / (10**decimals)
242
+ frac_part = scaled % (10**decimals)
243
+ str = int_part.to_s
244
+ str += ".#{frac_part.to_s.rjust(decimals, '0')}" if decimals.positive?
245
+ negative && scaled.positive? ? "-#{str}" : str
246
+ end
247
+
248
+ # http_build_query($data): nulls skipped, true/false as 1/0, nested
249
+ # arrays as key[0]=..., RFC 1738 encoding (space as +).
250
+ def http_build_query(data, prefix = nil)
251
+ parts = []
252
+ pairs(data).each do |key, value|
253
+ next if value.nil?
254
+
255
+ name = prefix ? "#{prefix}[#{key}]" : key.to_s
256
+ if array?(value)
257
+ nested = http_build_query(value, name)
258
+ parts << nested unless nested.empty?
259
+ else
260
+ parts << "#{urlencode(name)}=#{urlencode(query_scalar(value))}"
261
+ end
262
+ end
263
+ parts.join('&')
264
+ end
265
+
266
+ def query_scalar(value)
267
+ case value
268
+ when true then '1'
269
+ when false then '0'
270
+ else to_s(value)
271
+ end
272
+ end
273
+
274
+ # urlencode(): everything except [A-Za-z0-9_.-] is %XX, space is +.
275
+ def urlencode(value)
276
+ to_s(value).b.gsub(/[^A-Za-z0-9_.\-]/n) do |c|
277
+ c == ' ' ? '+' : format('%%%02X', c.ord)
278
+ end
279
+ end
280
+
281
+ # json_decode($body, true): nil for empty or invalid JSON.
282
+ def json_decode(body)
283
+ return nil unless body.is_a?(String)
284
+ body = body.dup.force_encoding(Encoding::UTF_8) unless body.encoding == Encoding::UTF_8
285
+ return nil unless body.valid_encoding?
286
+ return nil if body.strip.empty?
287
+
288
+ JSON.parse(body)
289
+ rescue JSON::ParserError, EncodingError
290
+ nil
291
+ end
292
+
293
+ # json_encode() of a value already built in PHP's shape.
294
+ def json_encode(value)
295
+ JSON.generate(value)
296
+ rescue StandardError
297
+ JSON.generate(scrub_utf8(value))
298
+ end
299
+
300
+ def scrub_utf8(value)
301
+ case value
302
+ when String then value.dup.force_encoding(Encoding::UTF_8).scrub("�")
303
+ when Hash then value.each_with_object({}) { |(k, v), h| h[scrub_utf8(k)] = scrub_utf8(v) }
304
+ when Array then value.map { |v| scrub_utf8(v) }
305
+ else value
306
+ end
307
+ end
308
+
309
+ # filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
310
+ IPV4_OCTET = '(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)'
311
+ IPV4_RE = /\A#{IPV4_OCTET}(\.#{IPV4_OCTET}){3}\z/.freeze
312
+
313
+ def ipv4?(value)
314
+ value.is_a?(String) || value.is_a?(Integer) ? IPV4_RE.match?(to_s(value)) : false
315
+ end
316
+
317
+ # filter_var($value, FILTER_VALIDATE_BOOLEAN)
318
+ def filter_bool(value)
319
+ %w[1 true on yes].include?(trim(value).downcase)
320
+ end
321
+ end
322
+ end