fabulous 0.1.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.txt +21 -0
- data/README.md +422 -0
- data/bin/fabulous +7 -0
- data/lib/fabulous/cli.rb +666 -0
- data/lib/fabulous/client.rb +89 -0
- data/lib/fabulous/configuration.rb +17 -0
- data/lib/fabulous/errors.rb +38 -0
- data/lib/fabulous/resources/base.rb +44 -0
- data/lib/fabulous/resources/dns.rb +185 -0
- data/lib/fabulous/resources/domain.rb +103 -0
- data/lib/fabulous/response.rb +226 -0
- data/lib/fabulous/version.rb +5 -0
- data/lib/fabulous.rb +33 -0
- metadata +285 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :configuration
|
|
6
|
+
|
|
7
|
+
def initialize(configuration = nil)
|
|
8
|
+
@configuration = configuration || Fabulous.configuration || Configuration.new
|
|
9
|
+
validate_configuration!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def domains
|
|
13
|
+
@domains ||= Resources::Domain.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def dns
|
|
17
|
+
@dns ||= Resources::DNS.new(self)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def request(action, params = {})
|
|
21
|
+
params = build_params(action, params)
|
|
22
|
+
|
|
23
|
+
# The Fabulous API uses GET requests with URL parameters
|
|
24
|
+
url = "/#{action}"
|
|
25
|
+
|
|
26
|
+
response = connection.get(url) do |req|
|
|
27
|
+
req.params = params
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Debug output if ENV variable is set
|
|
31
|
+
if ENV["DEBUG_FABULOUS"]
|
|
32
|
+
puts "Request URL: #{configuration.base_url}#{url}"
|
|
33
|
+
puts "Parameters: #{params.inspect}"
|
|
34
|
+
puts "HTTP Status: #{response.status}"
|
|
35
|
+
puts "Response Body:"
|
|
36
|
+
puts response.body
|
|
37
|
+
puts
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Response.new(response.body).tap do |parsed_response|
|
|
41
|
+
handle_errors(parsed_response)
|
|
42
|
+
end
|
|
43
|
+
rescue Faraday::TimeoutError => e
|
|
44
|
+
raise TimeoutError, "Request timed out: #{e.message}"
|
|
45
|
+
rescue Faraday::Error => e
|
|
46
|
+
raise RequestError, "Request failed: #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def connection
|
|
52
|
+
@connection ||= Faraday.new(url: configuration.base_url) do |faraday|
|
|
53
|
+
faraday.request :url_encoded
|
|
54
|
+
faraday.adapter Faraday.default_adapter
|
|
55
|
+
faraday.options.timeout = configuration.timeout
|
|
56
|
+
faraday.options.open_timeout = configuration.open_timeout
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_params(action, params)
|
|
61
|
+
# Don't include action in params, it's part of the URL path
|
|
62
|
+
{
|
|
63
|
+
username: configuration.username,
|
|
64
|
+
password: configuration.password
|
|
65
|
+
}.merge(params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def handle_errors(response)
|
|
69
|
+
return if response.success?
|
|
70
|
+
|
|
71
|
+
case response.status_code
|
|
72
|
+
when 300..399
|
|
73
|
+
raise AuthenticationError.new(response.status_message || "Authentication failed", response.status_code)
|
|
74
|
+
when 400..499
|
|
75
|
+
raise RequestError.new(response.status_message || "Request error", response.status_code)
|
|
76
|
+
when 500..599
|
|
77
|
+
raise ResponseError.new(response.status_message || "Server error", response.status_code)
|
|
78
|
+
when 689
|
|
79
|
+
raise RateLimitError, "Execution time exhausted (300 seconds per 24 hours limit reached)"
|
|
80
|
+
else
|
|
81
|
+
raise Error, "Unknown error: #{response.status_message} (code: #{response.status_code})"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_configuration!
|
|
86
|
+
raise ConfigurationError, "Username and password are required" unless configuration.valid?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :username, :password, :base_url, :timeout, :open_timeout
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@base_url = "https://api.fabulous.com"
|
|
9
|
+
@timeout = 30
|
|
10
|
+
@open_timeout = 10
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def valid?
|
|
14
|
+
!username.nil? && !password.nil?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ConfigurationError < Error; end
|
|
7
|
+
|
|
8
|
+
class AuthenticationError < Error
|
|
9
|
+
attr_reader :code
|
|
10
|
+
|
|
11
|
+
def initialize(message, code = nil)
|
|
12
|
+
@code = code
|
|
13
|
+
super(message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class RequestError < Error
|
|
18
|
+
attr_reader :code
|
|
19
|
+
|
|
20
|
+
def initialize(message, code = nil)
|
|
21
|
+
@code = code
|
|
22
|
+
super(message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ResponseError < Error
|
|
27
|
+
attr_reader :code
|
|
28
|
+
|
|
29
|
+
def initialize(message, code = nil)
|
|
30
|
+
@code = code
|
|
31
|
+
super(message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class RateLimitError < Error; end
|
|
36
|
+
|
|
37
|
+
class TimeoutError < Error; end
|
|
38
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
module Resources
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :client
|
|
7
|
+
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def request(action, params = {})
|
|
15
|
+
client.request(action, params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def paginate(action, params = {}, &block)
|
|
19
|
+
page = params.delete(:page) || 1
|
|
20
|
+
all_results = []
|
|
21
|
+
|
|
22
|
+
loop do
|
|
23
|
+
response = request(action, params.merge(page: page))
|
|
24
|
+
|
|
25
|
+
if block_given?
|
|
26
|
+
yield response, page
|
|
27
|
+
else
|
|
28
|
+
all_results.concat(extract_items(response))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
break unless response.paginated? && page < response.page_count
|
|
32
|
+
page += 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
block_given? ? nil : all_results
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def extract_items(response)
|
|
39
|
+
# Override in subclasses to extract the appropriate items
|
|
40
|
+
response.data.values.first || []
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
module Resources
|
|
5
|
+
class DNS < Base
|
|
6
|
+
def list_records(domain_name, type: nil)
|
|
7
|
+
params = { domain: domain_name }
|
|
8
|
+
params[:type] = type if type
|
|
9
|
+
|
|
10
|
+
response = request("listDNSrecords", params)
|
|
11
|
+
response.data[:dns_records] || []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# MX Records
|
|
15
|
+
def mx_records(domain_name)
|
|
16
|
+
response = request("getMXRecords", domain: domain_name)
|
|
17
|
+
response.data[:mx_records] || []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_mx_record(domain_name, hostname:, priority:, ttl: 3600)
|
|
21
|
+
response = request("addMXRecord", {
|
|
22
|
+
domain: domain_name,
|
|
23
|
+
hostname: hostname,
|
|
24
|
+
priority: priority,
|
|
25
|
+
ttl: ttl
|
|
26
|
+
})
|
|
27
|
+
response.success?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update_mx_record(domain_name, record_id:, hostname: nil, priority: nil, ttl: nil)
|
|
31
|
+
params = {
|
|
32
|
+
domain: domain_name,
|
|
33
|
+
recordId: record_id
|
|
34
|
+
}
|
|
35
|
+
params[:hostname] = hostname if hostname
|
|
36
|
+
params[:priority] = priority if priority
|
|
37
|
+
params[:ttl] = ttl if ttl
|
|
38
|
+
|
|
39
|
+
response = request("updateMXRecord", params)
|
|
40
|
+
response.success?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def delete_mx_record(domain_name, record_id)
|
|
44
|
+
response = request("deleteMXRecord", domain: domain_name, recordId: record_id)
|
|
45
|
+
response.success?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# CNAME Records
|
|
49
|
+
def cname_records(domain_name)
|
|
50
|
+
response = request("getCNAMERecords", domain: domain_name)
|
|
51
|
+
response.data[:cname_records] || []
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_cname_record(domain_name, alias_name:, target:, ttl: 3600)
|
|
55
|
+
response = request("addCNAMERecord", {
|
|
56
|
+
domain: domain_name,
|
|
57
|
+
alias: alias_name,
|
|
58
|
+
target: target,
|
|
59
|
+
ttl: ttl
|
|
60
|
+
})
|
|
61
|
+
response.success?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def update_cname_record(domain_name, record_id:, alias_name: nil, target: nil, ttl: nil)
|
|
65
|
+
params = {
|
|
66
|
+
domain: domain_name,
|
|
67
|
+
recordId: record_id
|
|
68
|
+
}
|
|
69
|
+
params[:alias] = alias_name if alias_name
|
|
70
|
+
params[:target] = target if target
|
|
71
|
+
params[:ttl] = ttl if ttl
|
|
72
|
+
|
|
73
|
+
response = request("updateCNAMERecord", params)
|
|
74
|
+
response.success?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def delete_cname_record(domain_name, record_id)
|
|
78
|
+
response = request("deleteCNAMERecord", domain: domain_name, recordId: record_id)
|
|
79
|
+
response.success?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# A Records
|
|
83
|
+
def a_records(domain_name)
|
|
84
|
+
response = request("getARecords", domain: domain_name)
|
|
85
|
+
response.data[:a_records] || []
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def add_a_record(domain_name, hostname:, ip_address:, ttl: 3600)
|
|
89
|
+
response = request("addARecord", {
|
|
90
|
+
domain: domain_name,
|
|
91
|
+
hostname: hostname,
|
|
92
|
+
ipAddress: ip_address,
|
|
93
|
+
ttl: ttl
|
|
94
|
+
})
|
|
95
|
+
response.success?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def update_a_record(domain_name, record_id:, hostname: nil, ip_address: nil, ttl: nil)
|
|
99
|
+
params = {
|
|
100
|
+
domain: domain_name,
|
|
101
|
+
recordId: record_id
|
|
102
|
+
}
|
|
103
|
+
params[:hostname] = hostname if hostname
|
|
104
|
+
params[:ipAddress] = ip_address if ip_address
|
|
105
|
+
params[:ttl] = ttl if ttl
|
|
106
|
+
|
|
107
|
+
response = request("updateARecord", params)
|
|
108
|
+
response.success?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def delete_a_record(domain_name, record_id)
|
|
112
|
+
response = request("deleteARecord", domain: domain_name, recordId: record_id)
|
|
113
|
+
response.success?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# TXT Records
|
|
117
|
+
def txt_records(domain_name)
|
|
118
|
+
response = request("getTXTRecords", domain: domain_name)
|
|
119
|
+
response.data[:txt_records] || []
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_txt_record(domain_name, hostname:, text:, ttl: 3600)
|
|
123
|
+
response = request("addTXTRecord", {
|
|
124
|
+
domain: domain_name,
|
|
125
|
+
hostname: hostname,
|
|
126
|
+
text: text,
|
|
127
|
+
ttl: ttl
|
|
128
|
+
})
|
|
129
|
+
response.success?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def update_txt_record(domain_name, record_id:, hostname: nil, text: nil, ttl: nil)
|
|
133
|
+
params = {
|
|
134
|
+
domain: domain_name,
|
|
135
|
+
recordId: record_id
|
|
136
|
+
}
|
|
137
|
+
params[:hostname] = hostname if hostname
|
|
138
|
+
params[:text] = text if text
|
|
139
|
+
params[:ttl] = ttl if ttl
|
|
140
|
+
|
|
141
|
+
response = request("updateTXTRecord", params)
|
|
142
|
+
response.success?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def delete_txt_record(domain_name, record_id)
|
|
146
|
+
response = request("deleteTXTRecord", domain: domain_name, recordId: record_id)
|
|
147
|
+
response.success?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# AAAA Records (IPv6)
|
|
151
|
+
def aaaa_records(domain_name)
|
|
152
|
+
response = request("getAAAARecords", domain: domain_name)
|
|
153
|
+
response.data[:aaaa_records] || []
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def add_aaaa_record(domain_name, hostname:, ipv6_address:, ttl: 3600)
|
|
157
|
+
response = request("addAAAARecord", {
|
|
158
|
+
domain: domain_name,
|
|
159
|
+
hostname: hostname,
|
|
160
|
+
ipv6Address: ipv6_address,
|
|
161
|
+
ttl: ttl
|
|
162
|
+
})
|
|
163
|
+
response.success?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def update_aaaa_record(domain_name, record_id:, hostname: nil, ipv6_address: nil, ttl: nil)
|
|
167
|
+
params = {
|
|
168
|
+
domain: domain_name,
|
|
169
|
+
recordId: record_id
|
|
170
|
+
}
|
|
171
|
+
params[:hostname] = hostname if hostname
|
|
172
|
+
params[:ipv6Address] = ipv6_address if ipv6_address
|
|
173
|
+
params[:ttl] = ttl if ttl
|
|
174
|
+
|
|
175
|
+
response = request("updateAAAARecord", params)
|
|
176
|
+
response.success?
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def delete_aaaa_record(domain_name, record_id)
|
|
180
|
+
response = request("deleteAAAARecord", domain: domain_name, recordId: record_id)
|
|
181
|
+
response.success?
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
module Resources
|
|
5
|
+
class Domain < Base
|
|
6
|
+
def list(page: nil, &block)
|
|
7
|
+
if page
|
|
8
|
+
response = request("listDomains", page: page)
|
|
9
|
+
block_given? ? yield(response) : response.data[:domains]
|
|
10
|
+
else
|
|
11
|
+
paginate("listDomains", &block)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def all
|
|
16
|
+
paginate("listDomains")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def check(domain_name)
|
|
20
|
+
response = request("checkDomain", domain: domain_name)
|
|
21
|
+
response.data[:available]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def info(domain_name)
|
|
25
|
+
response = request("domainInfo", domain: domain_name)
|
|
26
|
+
response.data[:domain_info]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def register(domain_name, years: 1, nameservers: [], whois_privacy: false, auto_renew: false)
|
|
30
|
+
params = {
|
|
31
|
+
domain: domain_name,
|
|
32
|
+
years: years,
|
|
33
|
+
whoisPrivacy: whois_privacy,
|
|
34
|
+
autoRenew: auto_renew
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
nameservers.each_with_index do |ns, index|
|
|
38
|
+
params["ns#{index + 1}"] = ns
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
response = request("registerDomain", params)
|
|
42
|
+
response.success?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def renew(domain_name, years: 1)
|
|
46
|
+
response = request("renewDomain", domain: domain_name, years: years)
|
|
47
|
+
response.success?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def transfer_in(domain_name, auth_code)
|
|
51
|
+
response = request("transferIn", domain: domain_name, authCode: auth_code)
|
|
52
|
+
response.success?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_nameservers(domain_name, nameservers)
|
|
56
|
+
params = { domain: domain_name }
|
|
57
|
+
|
|
58
|
+
nameservers.each_with_index do |ns, index|
|
|
59
|
+
params["ns#{index + 1}"] = ns
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
response = request("setNameServers", params)
|
|
63
|
+
response.success?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_nameservers(domain_name)
|
|
67
|
+
info = info(domain_name)
|
|
68
|
+
info[:nameservers] if info
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def lock(domain_name)
|
|
72
|
+
response = request("lockDomain", domain: domain_name)
|
|
73
|
+
response.success?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def unlock(domain_name)
|
|
77
|
+
response = request("unlockDomain", domain: domain_name)
|
|
78
|
+
response.success?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def set_auto_renew(domain_name, enabled: true)
|
|
82
|
+
response = request("setAutoRenew", domain: domain_name, autoRenew: enabled)
|
|
83
|
+
response.success?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def enable_whois_privacy(domain_name)
|
|
87
|
+
response = request("enableWhoisPrivacy", domain: domain_name)
|
|
88
|
+
response.success?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def disable_whois_privacy(domain_name)
|
|
92
|
+
response = request("disableWhoisPrivacy", domain: domain_name)
|
|
93
|
+
response.success?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
protected
|
|
97
|
+
|
|
98
|
+
def extract_items(response)
|
|
99
|
+
response.data[:domains] || []
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fabulous
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :doc, :raw_xml
|
|
6
|
+
|
|
7
|
+
def initialize(xml_string)
|
|
8
|
+
@raw_xml = xml_string
|
|
9
|
+
@doc = Nokogiri::XML(xml_string)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def success?
|
|
13
|
+
status_code && status_code.to_i == 200
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def status_code
|
|
17
|
+
# Try both old and new format
|
|
18
|
+
@status_code ||= doc.at_xpath("//statusCode")&.text&.to_i ||
|
|
19
|
+
doc.at_xpath("//response/status")&.text&.to_i
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def status_message
|
|
23
|
+
# Try both old and new format
|
|
24
|
+
@status_message ||= doc.at_xpath("//statusText")&.text ||
|
|
25
|
+
doc.at_xpath("//response/reason")&.text
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def data
|
|
29
|
+
@data ||= parse_data
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def paginated?
|
|
33
|
+
# Check if results count exceeds what's shown (pagination needed)
|
|
34
|
+
total = doc.at_xpath("//results")&.attr("count")&.to_i || 0
|
|
35
|
+
shown = doc.xpath("//results/result").length
|
|
36
|
+
total > shown && shown > 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def page_count
|
|
40
|
+
# Calculate based on total results and page size
|
|
41
|
+
total = doc.at_xpath("//results")&.attr("count")&.to_i || 0
|
|
42
|
+
page_size = doc.xpath("//results/result").length
|
|
43
|
+
return 1 if page_size == 0
|
|
44
|
+
(total.to_f / page_size).ceil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def current_page
|
|
48
|
+
# Try to get from request params
|
|
49
|
+
doc.at_xpath("//request/params/param[@name='page']")&.text&.to_i || 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def parse_data
|
|
55
|
+
result = {}
|
|
56
|
+
|
|
57
|
+
# Parse new format with results
|
|
58
|
+
if results = doc.xpath("//results/result")
|
|
59
|
+
result[:domains] = parse_results_domains(results)
|
|
60
|
+
# Parse old format
|
|
61
|
+
elsif domains = doc.xpath("//domain")
|
|
62
|
+
result[:domains] = parse_domains(domains)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if dns_records = doc.xpath("//dnsrecord")
|
|
66
|
+
result[:dns_records] = parse_dns_records(dns_records)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if mx_records = doc.xpath("//mxrecord")
|
|
70
|
+
result[:mx_records] = parse_mx_records(mx_records)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if cname_records = doc.xpath("//cnamerecord")
|
|
74
|
+
result[:cname_records] = parse_cname_records(cname_records)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if a_records = doc.xpath("//arecord")
|
|
78
|
+
result[:a_records] = parse_a_records(a_records)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Parse single domain info
|
|
82
|
+
if domain_info = doc.at_xpath("//domainInfo")
|
|
83
|
+
result[:domain_info] = parse_domain_info(domain_info)
|
|
84
|
+
elsif info_result = doc.at_xpath("//results/result[expiry]")
|
|
85
|
+
# domainInfo response format
|
|
86
|
+
result[:domain_info] = {
|
|
87
|
+
expiry_date: info_result.at_xpath("expiry")&.text,
|
|
88
|
+
nameservers: info_result.xpath("nameserverss/nameservers").map(&:text),
|
|
89
|
+
status: info_result.at_xpath("fabstatus")&.text&.capitalize || "Active",
|
|
90
|
+
auto_renew: info_result.at_xpath("autorenewstatus")&.text == "1",
|
|
91
|
+
locked: info_result.xpath("registrystatuss/registrystatus").any? { |s| s.text.include?("Prohibited") },
|
|
92
|
+
whois_privacy: info_result.at_xpath("whoisprivacyenabled")&.text == "1"
|
|
93
|
+
}
|
|
94
|
+
elsif domain_element = doc.at_xpath("//domain")
|
|
95
|
+
# Alternative format for domain info
|
|
96
|
+
result[:domain_info] = {
|
|
97
|
+
status: domain_element.at_xpath("status")&.text || "Active"
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Parse availability check
|
|
102
|
+
if availability = doc.at_xpath("//availability")
|
|
103
|
+
result[:available] = availability.text == "true"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
result.empty? ? parse_generic : result
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_results_domains(results)
|
|
110
|
+
results.map do |result|
|
|
111
|
+
{
|
|
112
|
+
name: result.at_xpath("domain")&.text,
|
|
113
|
+
expiry_date: result.at_xpath("exdate")&.text,
|
|
114
|
+
status: "Active", # Not provided in new format, assuming active
|
|
115
|
+
auto_renew: nil, # Not provided in this format
|
|
116
|
+
locked: nil # Not provided in this format
|
|
117
|
+
}.compact
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_domains(domains)
|
|
122
|
+
domains.map do |domain|
|
|
123
|
+
{
|
|
124
|
+
name: domain.at_xpath("name")&.text,
|
|
125
|
+
status: domain.at_xpath("status")&.text,
|
|
126
|
+
expiry_date: domain.at_xpath("expiryDate")&.text,
|
|
127
|
+
auto_renew: domain.at_xpath("autoRenew")&.text == "true",
|
|
128
|
+
locked: domain.at_xpath("locked")&.text == "true"
|
|
129
|
+
}.compact
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_domain_info(info)
|
|
134
|
+
{
|
|
135
|
+
name: info.at_xpath("name")&.text,
|
|
136
|
+
status: info.at_xpath("status")&.text,
|
|
137
|
+
creation_date: info.at_xpath("creationDate")&.text,
|
|
138
|
+
expiry_date: info.at_xpath("expiryDate")&.text,
|
|
139
|
+
nameservers: info.xpath("nameservers/nameserver").map(&:text),
|
|
140
|
+
auto_renew: info.at_xpath("autoRenew")&.text == "true",
|
|
141
|
+
locked: info.at_xpath("locked")&.text == "true",
|
|
142
|
+
whois_privacy: info.at_xpath("whoisPrivacy")&.text == "true"
|
|
143
|
+
}.compact
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def parse_dns_records(records)
|
|
147
|
+
records.map do |record|
|
|
148
|
+
{
|
|
149
|
+
id: record.at_xpath("id")&.text,
|
|
150
|
+
type: record.at_xpath("type")&.text,
|
|
151
|
+
name: record.at_xpath("name")&.text,
|
|
152
|
+
value: record.at_xpath("value")&.text,
|
|
153
|
+
ttl: record.at_xpath("ttl")&.text&.to_i,
|
|
154
|
+
priority: record.at_xpath("priority")&.text&.to_i
|
|
155
|
+
}.compact
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def parse_mx_records(records)
|
|
160
|
+
records.map do |record|
|
|
161
|
+
{
|
|
162
|
+
id: record.at_xpath("id")&.text,
|
|
163
|
+
hostname: record.at_xpath("hostname")&.text,
|
|
164
|
+
priority: record.at_xpath("priority")&.text&.to_i,
|
|
165
|
+
ttl: record.at_xpath("ttl")&.text&.to_i
|
|
166
|
+
}.compact
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_cname_records(records)
|
|
171
|
+
records.map do |record|
|
|
172
|
+
{
|
|
173
|
+
id: record.at_xpath("id")&.text,
|
|
174
|
+
alias: record.at_xpath("alias")&.text,
|
|
175
|
+
target: record.at_xpath("target")&.text,
|
|
176
|
+
ttl: record.at_xpath("ttl")&.text&.to_i
|
|
177
|
+
}.compact
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def parse_a_records(records)
|
|
182
|
+
records.map do |record|
|
|
183
|
+
{
|
|
184
|
+
id: record.at_xpath("id")&.text,
|
|
185
|
+
hostname: record.at_xpath("hostname")&.text,
|
|
186
|
+
ip_address: record.at_xpath("ipAddress")&.text,
|
|
187
|
+
ttl: record.at_xpath("ttl")&.text&.to_i
|
|
188
|
+
}.compact
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def parse_generic
|
|
193
|
+
# Return all non-status elements as a hash
|
|
194
|
+
result = {}
|
|
195
|
+
doc.root.children.each do |child|
|
|
196
|
+
next if child.text? || child.name =~ /status/i
|
|
197
|
+
|
|
198
|
+
if child.children.length > 1
|
|
199
|
+
result[child.name.to_sym] = parse_element(child)
|
|
200
|
+
else
|
|
201
|
+
result[child.name.to_sym] = child.text
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
result
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def parse_element(element)
|
|
208
|
+
if element.children.all? { |c| c.text? }
|
|
209
|
+
element.text
|
|
210
|
+
else
|
|
211
|
+
result = {}
|
|
212
|
+
element.children.each do |child|
|
|
213
|
+
next if child.text?
|
|
214
|
+
|
|
215
|
+
if result[child.name.to_sym]
|
|
216
|
+
result[child.name.to_sym] = [result[child.name.to_sym]] unless result[child.name.to_sym].is_a?(Array)
|
|
217
|
+
result[child.name.to_sym] << parse_element(child)
|
|
218
|
+
else
|
|
219
|
+
result[child.name.to_sym] = parse_element(child)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
result
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|