enom-api 0.1.1
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/.document +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/enom-api.gemspec +61 -0
- data/lib/enom-api.rb +24 -0
- data/lib/enom-api/client.rb +729 -0
- data/lib/enom-api/interface.rb +66 -0
- data/lib/enom-api/registrant.rb +91 -0
- data/lib/enom-api/search_query.rb +51 -0
- data/test/helper.rb +10 -0
- data/test/test_enom-api.rb +7 -0
- metadata +124 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Geoff Garside
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= eNom API Client
|
2
|
+
|
3
|
+
Provides a Ruby client for the eNom Reseller API.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Geoff Garside. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "enom-api"
|
8
|
+
gem.summary = %Q{eNom API Client}
|
9
|
+
gem.description = %Q{Client for communicating with the eNom API}
|
10
|
+
gem.email = "geoff@geoffgarside.co.uk"
|
11
|
+
gem.homepage = "http://github.com/geoffgarside/enom-api"
|
12
|
+
gem.authors = ["Geoff Garside"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
gem.add_dependency "demolisher", ">= 0.6.0"
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
25
|
+
test.libs << 'lib' << 'test'
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
27
|
+
test.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |test|
|
33
|
+
test.libs << 'test'
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :test => :check_dependencies
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'yard'
|
49
|
+
YARD::Rake::YardocTask.new
|
50
|
+
rescue LoadError
|
51
|
+
task :yardoc do
|
52
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
53
|
+
end
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/enom-api.gemspec
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{enom-api}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Geoff Garside"]
|
12
|
+
s.date = %q{2011-03-29}
|
13
|
+
s.description = %q{Client for communicating with the eNom API}
|
14
|
+
s.email = %q{geoff@geoffgarside.co.uk}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"enom-api.gemspec",
|
26
|
+
"lib/enom-api.rb",
|
27
|
+
"lib/enom-api/client.rb",
|
28
|
+
"lib/enom-api/interface.rb",
|
29
|
+
"lib/enom-api/registrant.rb",
|
30
|
+
"lib/enom-api/search_query.rb",
|
31
|
+
"test/helper.rb",
|
32
|
+
"test/test_enom-api.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/geoffgarside/enom-api}
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.6.2}
|
37
|
+
s.summary = %q{eNom API Client}
|
38
|
+
s.test_files = [
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_enom-api.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
49
|
+
s.add_runtime_dependency(%q<demolisher>, [">= 0.6.0"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
52
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
53
|
+
s.add_dependency(%q<demolisher>, [">= 0.6.0"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
57
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
58
|
+
s.add_dependency(%q<demolisher>, [">= 0.6.0"])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
data/lib/enom-api.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'xml'
|
2
|
+
require 'demolisher'
|
3
|
+
|
4
|
+
module EnomAPI # :nodoc:
|
5
|
+
autoload :Client, File.dirname(__FILE__) + '/enom-api/client.rb'
|
6
|
+
autoload :Interface, File.dirname(__FILE__) + '/enom-api/interface.rb'
|
7
|
+
autoload :Registrant, File.dirname(__FILE__) + '/enom-api/registrant.rb'
|
8
|
+
autoload :SearchQuery, File.dirname(__FILE__) + '/enom-api/search_query.rb'
|
9
|
+
|
10
|
+
# API Response error exception
|
11
|
+
class ResponseError < RuntimeError
|
12
|
+
attr_reader :messages
|
13
|
+
def initialize(error_messages)
|
14
|
+
@messages = error_messages
|
15
|
+
end
|
16
|
+
end
|
17
|
+
# API Incomplete response error
|
18
|
+
class IncompleteResponseError < RuntimeError
|
19
|
+
attr_reader :xml
|
20
|
+
def initialize(xml)
|
21
|
+
@xml = xml
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,729 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'demolisher'
|
3
|
+
|
4
|
+
module EnomAPI
|
5
|
+
# Client
|
6
|
+
class Client
|
7
|
+
# @param [String] user eNom Account ID
|
8
|
+
# @param [String] passwd eNom Account Password
|
9
|
+
# @param [String] server Server to connect to. Use 'resellertest.enom.com' for test.
|
10
|
+
def initialize(user, passwd, server = 'reseller.enom.com')
|
11
|
+
@user, @server = user, server
|
12
|
+
@conn = Interface.new(user, passwd, server)
|
13
|
+
end
|
14
|
+
def inspect # :nodoc:
|
15
|
+
"#<#{self.class} #{@user}@#{@server}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Perform a search.
|
19
|
+
#
|
20
|
+
# The returned array contains hashes with the following keys
|
21
|
+
# - (String) +:id+ -- Domain ID within the eNom registry
|
22
|
+
# - (String) +:name+ -- Domain name
|
23
|
+
# - (BOOL) +:auto_renew+ -- Whether auto-renew is set on the domain
|
24
|
+
# - (Time) +:expires+ -- Expiration date of the domain
|
25
|
+
# - (String) +:status+ -- Registration status of the domain
|
26
|
+
# - (Array) +:nameservers+ -- Nameserver names
|
27
|
+
#
|
28
|
+
# @yield [q] block to build up search query
|
29
|
+
# @yieldparam [SearchQuery] q SearchQuery instance
|
30
|
+
# @return [Array] Array of hashes of search results
|
31
|
+
# including domain ID, name, auto_renew status, expiration date
|
32
|
+
# status and nameservers
|
33
|
+
def search(&block)
|
34
|
+
response = @conn.search(&block)
|
35
|
+
xml = XML::Parser.string(response).parse
|
36
|
+
|
37
|
+
o = Hash.new
|
38
|
+
d = Demolisher.demolish(xml)
|
39
|
+
d.send("interface-response") do
|
40
|
+
d.DomainSearch do
|
41
|
+
o[:total_results] = d.TotalResults.to_s.to_i
|
42
|
+
o[:start_position] = d.StartPosition.to_s.to_i
|
43
|
+
o[:next_position] = d.NextPosition.to_s.to_i
|
44
|
+
|
45
|
+
d.Domains do
|
46
|
+
o[:results] = Array.new
|
47
|
+
d.Domain do
|
48
|
+
o[:results] << {
|
49
|
+
:id => d.DomainNameID,
|
50
|
+
:name => "#{d.SLD}.#{d.TLD}",
|
51
|
+
:auto_renew => d.AutoRenew?,
|
52
|
+
:expires => Time.parse(d.ExpDate),
|
53
|
+
:status => d.DomainRegistrationStatus,
|
54
|
+
:nameservers => (d.NameServers && d.NameServers.split(",")) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
o
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks status of domain names
|
63
|
+
#
|
64
|
+
# @param [Array<String>] *names Names of domains to check the status of.
|
65
|
+
# @return [Boolean] when 1 name provided, whether the domain is available or not
|
66
|
+
# @return [Hash<String, Boolean>] when multiple names provided, hash of names
|
67
|
+
# and whether the domain is available or not
|
68
|
+
# @raise [ArgumentError] if more than 30 names are provided
|
69
|
+
def check(*names)
|
70
|
+
raise ArgumentError, "maximum number of names is 30" if names.size > 30
|
71
|
+
xml = send_recv(:Check, :DomainList => names.join(','))
|
72
|
+
|
73
|
+
info = (1..xml.DomainCount.to_i).map do |i|
|
74
|
+
[xml.send("Domain#{i}"), xml.send("RRPCode#{i}") == '210']
|
75
|
+
end.flatten
|
76
|
+
|
77
|
+
return info[1] if info.size == 2
|
78
|
+
Hash[*info]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks the status of a nameserver registered with eNom.
|
82
|
+
#
|
83
|
+
# @param [String] name nameserver to check
|
84
|
+
# @return [Hash] the nameserver +:name+ and +:ipaddress+
|
85
|
+
# @return [false] the nameserver is not registered with eNom
|
86
|
+
def check_ns_status(name)
|
87
|
+
xml = send_recv(:CheckNSStatus, :CheckNSName => name)
|
88
|
+
|
89
|
+
return false if xml.RRPCode != '200'
|
90
|
+
{ :name => xml.CheckNsStatus.name, :ipaddress => xml.CheckNsStatus.ipaddress }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delete a registered nameserver from eNom
|
94
|
+
#
|
95
|
+
# @param [String] name nameserver to delete from the registry
|
96
|
+
# @return [Boolean] +true+ if deleted successfully, +false+ if not
|
97
|
+
def delete_nameserver(name)
|
98
|
+
xml = send_recv(:DeleteNameServer, :NS => name)
|
99
|
+
return xml.RRPCode == '200' && xml.NsSuccess == '1'
|
100
|
+
end
|
101
|
+
|
102
|
+
# Deletes a domain registration.
|
103
|
+
#
|
104
|
+
# The domain registration must be less than 5 days old. eNom requires an +EndUserIP+
|
105
|
+
# to be sent with the request, this is set to +127.0.0.1+.
|
106
|
+
#
|
107
|
+
# @param [String] domain Name of the registered domain
|
108
|
+
# @return [true] if successfully deleted
|
109
|
+
# @return [Hash] Error details with +:string+, +:source+ and +:section+ information
|
110
|
+
def delete_registration(domain)
|
111
|
+
xml = send_recv(:DeleteRegistration, split_domain(domain).merge(:EndUserIP => "127.000.000.001"))
|
112
|
+
|
113
|
+
return true if xml.DomainDeleted?
|
114
|
+
|
115
|
+
{ :string => xml.ErrString,
|
116
|
+
:source => xml.ErrSource,
|
117
|
+
:section => xml.ErrSection }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Gets the list of nameserver for a domain.
|
121
|
+
#
|
122
|
+
# @param [String] domain name of domain to collect nameservers of
|
123
|
+
# @return [Array<String>] array of nameservers
|
124
|
+
# @return [false] no nameservers for the domain
|
125
|
+
def get_dns(domain)
|
126
|
+
xml = send_recv(:GetDNS, split_domain(domain))
|
127
|
+
return false if xml.RRPCode != '200'
|
128
|
+
|
129
|
+
nameservers = []
|
130
|
+
xml.dns { nameservers << xml.strip }
|
131
|
+
nameservers
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get the number of domains in the account in specific groups.
|
135
|
+
#
|
136
|
+
# The groups of domains and their result keys are:
|
137
|
+
# - (Integer) +:registered+ -- Registered
|
138
|
+
# - (Integer) +:hosted+ -- Hosted
|
139
|
+
# - (Integer) +:expiring+ -- Expiring
|
140
|
+
# - (Integer) +:expired+ -- Expired
|
141
|
+
# - (Integer) +:redemption+ -- Redemption
|
142
|
+
# - (Integer) +:extended_redemption+ -- Extended Redemption
|
143
|
+
# - (Integer) +:processing+ -- Processing
|
144
|
+
# - (Integer) +:watch_list+ -- Watch List
|
145
|
+
#
|
146
|
+
# @return [Hash] Hash of number of domains in each group
|
147
|
+
def get_domain_count
|
148
|
+
xml = send_recv(:GetDomainCount)
|
149
|
+
|
150
|
+
{ :registered => xml.RegisteredCount.to_i,
|
151
|
+
:hosted => xml.HostCount.to_i,
|
152
|
+
:expiring => xml.ExpiringCount.to_i,
|
153
|
+
:expired => xml.ExpiredDomainsCount.to_i,
|
154
|
+
:redemption => xml.RGP.to_i,
|
155
|
+
:extended_redemption => xml.ExtendedRGP.to_i,
|
156
|
+
:processing => xml.ProcessCount.to_i,
|
157
|
+
:watch_list => xml.WatchlistCount.to_i }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Get the expiration date of a domain
|
161
|
+
#
|
162
|
+
# @param [String] domain Domain name
|
163
|
+
# @return [Time] expiration date of the domain
|
164
|
+
def get_domain_exp(domain)
|
165
|
+
xml = send_recv(:GetDomainExp, split_domain(domain))
|
166
|
+
Time.parse(xml.ExpirationDate.strip)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Get the domain information for a domain
|
170
|
+
#
|
171
|
+
# The information returned includes the following
|
172
|
+
# - (Time) +:expires+ -- Expiration date
|
173
|
+
# - (String) +:status+ -- Status
|
174
|
+
# - (Array) +:nameservers+ -- Nameserver names
|
175
|
+
#
|
176
|
+
# @param [String] domain Domain name
|
177
|
+
# @return [Hash] information for the domain
|
178
|
+
def get_domain_info(domain)
|
179
|
+
xml = send_recv(:GetDomainInfo, split_domain(domain))
|
180
|
+
xml = xml.GetDomainInfo
|
181
|
+
|
182
|
+
nameservers = []
|
183
|
+
xml.services.entry do |entry,_|
|
184
|
+
next unless entry['name'] == 'dnsserver'
|
185
|
+
entry.configuration.dns do |dns,_|
|
186
|
+
nameservers << dns.to_s
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
{ :expires => Time.parse(xml.status.expiration.strip),
|
191
|
+
:status => xml.status.registrationstatus.strip,
|
192
|
+
:nameservers => nameservers }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Get the registration status of a domain. Used for TLDs which
|
196
|
+
# do not register in real time.
|
197
|
+
#
|
198
|
+
# The hash returned includes the following information:
|
199
|
+
# - (String) +:order_id+ -- Order ID
|
200
|
+
# - (Integer) +:in_account+ -- In Account, one of 0, 1, 2
|
201
|
+
# - (String) +:description+ -- Description of In Account
|
202
|
+
# - (Time) +:expires+ -- Expiration Date
|
203
|
+
#
|
204
|
+
# The +:in_account+ field will have one of the following numeric values and meanings
|
205
|
+
# - 0: Domain is not in the eNom database
|
206
|
+
# - 1: Domain is in the eNom database and the recievers account
|
207
|
+
# - 2: Domain is in the eNom database but not the receivers account
|
208
|
+
#
|
209
|
+
# The +:description+ field contains a textual representation of the +:in_account+
|
210
|
+
# value, it will not necessarily match those given above. The meanings however
|
211
|
+
# should be correct.
|
212
|
+
#
|
213
|
+
# @overload get_domain_status(domain)
|
214
|
+
# @param [String] domain Name of the domain to get status for
|
215
|
+
# @overload get_domain_status(domain, order_id, order_type = :purchase)
|
216
|
+
# @param [String] domain Name of the domain to get status for
|
217
|
+
# @param [String] order_id Order ID to get information for
|
218
|
+
# @param [Symbol] order_type Type of order information to obtain, +:purchase+, +:transfer+, or +:extend+
|
219
|
+
# @return [Hash] Hash of registration information
|
220
|
+
def get_domain_status(domain, order_id = nil, order_type = :purchase)
|
221
|
+
order_opts = order_id.nil? ? {} : { :OrderID => order_id, :OrderType => order_type }
|
222
|
+
xml = send_recv(:GetDomainStatus, split_domain(domain).merge(order_opts))
|
223
|
+
|
224
|
+
{ :orderid => xml.OrderID,
|
225
|
+
:in_account => xml.InAccount.to_i,
|
226
|
+
:description => xml.StatusDesc,
|
227
|
+
:expires => Time.parse(xml.ExpDate.strip) }
|
228
|
+
end
|
229
|
+
|
230
|
+
# Get a list of the domains in the Expired, Redemption and Extended Redemption groups.
|
231
|
+
#
|
232
|
+
# The returned hash has the following keys
|
233
|
+
# - (Array) +:expired+ -- Expired domains
|
234
|
+
# - (Array) +:redemption+ -- Domains in Redemption Grace Period (RGP)
|
235
|
+
# - (Array) +:extended_redemption+ -- Domains in Extended RGP
|
236
|
+
#
|
237
|
+
# Each array contains hashes with the following keys
|
238
|
+
# - (String) +:name+ -- The domain name
|
239
|
+
# - (String) +:id+ -- The domains eNom registry ID
|
240
|
+
# - (Time) +:date+ -- Expiration date of the domain
|
241
|
+
# - (BOOL) +:locked+ -- Domain locked status
|
242
|
+
#
|
243
|
+
# @return [Hash] Hash of expired domains information
|
244
|
+
def get_expired_domains
|
245
|
+
xml = send_recv(:GetExpiredDomains)
|
246
|
+
|
247
|
+
domains = {:expired => [], :extended_redemption => [], :redemption => []}
|
248
|
+
xml.DomainDetail do
|
249
|
+
case xml.status
|
250
|
+
when /Expired/i
|
251
|
+
domains[:expired]
|
252
|
+
when /Extended RGP/i
|
253
|
+
domains[:extended_redemption]
|
254
|
+
when /RGP/i
|
255
|
+
domains[:redemption]
|
256
|
+
end << {
|
257
|
+
:name => xml.DomainName,
|
258
|
+
:id => xml.DomainNameID,
|
259
|
+
:date => Time.parse(xml.send('expiration-date')),
|
260
|
+
:locked => xml.lockstatus =~ /Locked/i
|
261
|
+
}
|
262
|
+
end
|
263
|
+
domains
|
264
|
+
end
|
265
|
+
|
266
|
+
# @param [String] domain Domain name to renew
|
267
|
+
# @param [String] period Number of years to extend the registration by
|
268
|
+
# @return [String] Order ID of the renewal
|
269
|
+
# @return [false] the order did not succeed
|
270
|
+
def extend(domain, period = 1)
|
271
|
+
xml = send_recv(:Extend, split_domain(domain).merge(:NumYears => period.to_i))
|
272
|
+
|
273
|
+
return false if xml.RRPCode != '200'
|
274
|
+
xml.OrderID
|
275
|
+
end
|
276
|
+
|
277
|
+
# Get the list of extended attributes required by a TLD
|
278
|
+
#
|
279
|
+
# The returned array of extended attributes contains hashes of the attribute details.
|
280
|
+
# The details include the following information
|
281
|
+
# - (String) +:id+ -- eNom internal attribute ID
|
282
|
+
# - (String) +:name+ -- Form parameter name
|
283
|
+
# - (String) +:title+ -- Short definition of the parameter value
|
284
|
+
# - (BOOL) +:application+ -- Attribute required for Registrant contact
|
285
|
+
# - (BOOL) +:user_defined+ -- Attribute value must be provided by user
|
286
|
+
# - (BOOL) +:required+ -- Attribute is required
|
287
|
+
# - (String) +:description+ -- Long definition of the parameter value
|
288
|
+
# - (String) +:is_child+ -- Is a child of another
|
289
|
+
# - (Array) +:options+ -- Array of options for the attribute
|
290
|
+
#
|
291
|
+
# Attribute options include the following information
|
292
|
+
# - (String) +:id+ -- eNom internal attribute option ID
|
293
|
+
# - (String) +:value+ -- Value of the option
|
294
|
+
# - (String) +:title+ -- Short definition of the parameter value
|
295
|
+
# - (String) +:description+ -- Long definition of the parameter value
|
296
|
+
#
|
297
|
+
# @param [String] tld Top Level Domain
|
298
|
+
# @return [Array] extended attributes, their details and valid options
|
299
|
+
def get_ext_attributes(tld)
|
300
|
+
xml = send_recv(:GetExtAttributes, :TLD => tld)
|
301
|
+
|
302
|
+
attrs = []
|
303
|
+
xml.Attributes do
|
304
|
+
xml.Attribute do
|
305
|
+
h = {
|
306
|
+
:id => xml.ID,
|
307
|
+
:name => xml.Name,
|
308
|
+
:title => xml.Title,
|
309
|
+
:application => xml.Application == '2',
|
310
|
+
:user_defined => xml.UserDefined?,
|
311
|
+
:required => xml.Required?,
|
312
|
+
:description => xml.Description,
|
313
|
+
:is_child => xml.IsChild?,
|
314
|
+
:options => Array.new }
|
315
|
+
attrs << h
|
316
|
+
xml.Options do
|
317
|
+
xml.Option do
|
318
|
+
h[:options] << {
|
319
|
+
:id => xml.ID,
|
320
|
+
:value => xml.Value,
|
321
|
+
:title => xml.Title,
|
322
|
+
:description => xml.Description
|
323
|
+
}
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
attrs
|
329
|
+
end
|
330
|
+
|
331
|
+
# Get renewal information for a domain
|
332
|
+
#
|
333
|
+
# The returned hash contains the following keys
|
334
|
+
# - (Time) +:expiration+ -- Time the domain expires
|
335
|
+
# - (Integer) +:max_extension+ -- Maximum number of years which can be added
|
336
|
+
# - (Integer) +:min_extension+ -- Minimum number of years which can be added
|
337
|
+
# - (BOOL) +:registrar_hold+ -- Registrar hold state
|
338
|
+
# - (Float) +:balance+ -- Current account balance
|
339
|
+
# - (Float) +:available+ -- Available account balance
|
340
|
+
#
|
341
|
+
# @param [String] domain Domain name
|
342
|
+
# @return [Hash] Renewal information
|
343
|
+
def get_extend_info(domain)
|
344
|
+
xml = send_recv(:GetExtendInfo, split_domain(domain))
|
345
|
+
|
346
|
+
{ :expiration => Time.parse(xml.Expiration),
|
347
|
+
:max_extension => xml.MaxExtension.to_i,
|
348
|
+
:min_extension => xml.MinAllowed.to_i,
|
349
|
+
:registrar_hold => xml.RegistrarHold?,
|
350
|
+
:balance => xml.Balance.to_f,
|
351
|
+
:available => xml.AvailableBalance.to_f }
|
352
|
+
end
|
353
|
+
|
354
|
+
# Get detailed information about an order
|
355
|
+
#
|
356
|
+
# The returned hash contains the following keys
|
357
|
+
# - (Boolean) +:result+ -- Order exists
|
358
|
+
# - (Float) +:amount+ -- Billed amount
|
359
|
+
# - (Array) +:details+ -- Details
|
360
|
+
# - (String) +:status+ -- Order Status
|
361
|
+
#
|
362
|
+
# The +:details+ result key array contains hashes with the following keys
|
363
|
+
# - (String) +:product_type+ -- Order detail item type
|
364
|
+
# - (String) +:description+ -- Description of the detail
|
365
|
+
# - (String) +:status+ -- Status of the order detail
|
366
|
+
# - (Integer) +:quantity+ -- Number of the details of this type
|
367
|
+
# - (Float) +:amount+ -- Amount paid for detail
|
368
|
+
#
|
369
|
+
# @param [String] order_id ID of the order
|
370
|
+
# @return [Hash] order information
|
371
|
+
def get_order_detail(order_id)
|
372
|
+
xml = send_recv(:GetOrderDetail, :OrderID => order_id)
|
373
|
+
|
374
|
+
info = {}
|
375
|
+
xml.Order do
|
376
|
+
info[:result] = xml.Result?
|
377
|
+
info[:amount] = xml.OrderBillAmount
|
378
|
+
info[:status] = xml.OrderStatus # If this doesn't exist, then its under OrderDetail
|
379
|
+
info[:details] = []
|
380
|
+
|
381
|
+
xml.OrderDetail do
|
382
|
+
info[:details] << {
|
383
|
+
:product_type => xml.ProductType,
|
384
|
+
:description => xml.Description,
|
385
|
+
:status => xml.Status,
|
386
|
+
:quantity => xml.Quantity.to_i,
|
387
|
+
:amount => xml.AmountPaid
|
388
|
+
}
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Get list of the account orders
|
394
|
+
#
|
395
|
+
# The returned array contains hashes with the following keys
|
396
|
+
# - (String) +:id+ -- Order ID number
|
397
|
+
# - (Time) +:date+ -- Date the order was placed
|
398
|
+
# - (String) +:status+ -- Status of the order
|
399
|
+
# - (BOOL) +:processed+ -- Whether the order has been processed
|
400
|
+
#
|
401
|
+
# @param [Hash] options Options to get the order list with
|
402
|
+
# @option options [Integer] :start Starting offset in order list
|
403
|
+
# @option options [String, #strftime] :begin String date or Date of earliest order to retrieve.
|
404
|
+
# If omitted then 6 months of orders are retrieved
|
405
|
+
# @option options [String, #strftime] :end String date or Date or lastest order to retrieve.
|
406
|
+
# If omitted then the end is today
|
407
|
+
# @return [Array] orders of :id, :date, :status and :processed
|
408
|
+
def get_order_list(options = {})
|
409
|
+
xml = send_recv(:GetOrderList, :Start => (options[:start] || 1)) do |h|
|
410
|
+
h[:BeginDate] = if options[:begin].respond_to?(:strftime)
|
411
|
+
options[:begin].strftime("%m/%d/%Y")
|
412
|
+
else
|
413
|
+
options[:begin]
|
414
|
+
end
|
415
|
+
|
416
|
+
h[:EndDate] = if options[:end].respond_to?(:strftime)
|
417
|
+
options[:end].strftime("%m/%d/%Y")
|
418
|
+
else
|
419
|
+
options[:end]
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
out = []
|
424
|
+
xml.OrderList do
|
425
|
+
xml.OrderDetail do
|
426
|
+
{ :id => xml.OrderID,
|
427
|
+
:date => Time.parse(xml.OrderDate),
|
428
|
+
:status => xml.StatusDesc,
|
429
|
+
:processed => xml.OrderProcessFlag? }
|
430
|
+
end
|
431
|
+
end
|
432
|
+
out
|
433
|
+
end
|
434
|
+
|
435
|
+
# Get the registration status of a domain.
|
436
|
+
#
|
437
|
+
# The returned hash has the following keys
|
438
|
+
# - (BOOL) +:hold+ -- Registrar hold set
|
439
|
+
# - (String) +:registration+ -- Registration status of the domain
|
440
|
+
# - (String) +:purchase+ -- Purchase status of the domain
|
441
|
+
#
|
442
|
+
# Registration status will be one of
|
443
|
+
# 1. Processing
|
444
|
+
# 2. Registered
|
445
|
+
# 3. Hosted
|
446
|
+
# 4. Null
|
447
|
+
#
|
448
|
+
# Purchase status will be one of
|
449
|
+
# 1. Processing
|
450
|
+
# 2. Paid
|
451
|
+
# 3. Null
|
452
|
+
#
|
453
|
+
# @param [String] domain Domain name to get registration status of
|
454
|
+
# @return [Hash] :hold and :status
|
455
|
+
def get_registration_status(domain)
|
456
|
+
xml = send_recv(:GetRegistrationStatus, split_domain(domain))
|
457
|
+
{ :hold => xml.RegistrarHold?, :registration => xml.RegistrationStatus, :purchase => xml.PurchaseStatus }
|
458
|
+
end
|
459
|
+
|
460
|
+
# Get the registrar lock setting for a domain
|
461
|
+
#
|
462
|
+
# @param [String] domain Domain name to check registrar lock of
|
463
|
+
# @return [Boolean] locked state, true = locked
|
464
|
+
def get_reg_lock(domain)
|
465
|
+
xml = send_recv(:GetRegLock, split_domain(domain))
|
466
|
+
xml.RegLock?
|
467
|
+
end
|
468
|
+
|
469
|
+
# Get the list of TLDs available for the account
|
470
|
+
#
|
471
|
+
# @return [Array<String>] array of TLDs available to the account
|
472
|
+
def get_tld_list
|
473
|
+
xml = send_recv(:GetTLDList)
|
474
|
+
|
475
|
+
tlds = []
|
476
|
+
xml.tldlist.tld do |tld,_|
|
477
|
+
tld.tld do
|
478
|
+
tlds << tld.to_s unless tld.to_s == ''
|
479
|
+
end
|
480
|
+
end
|
481
|
+
tlds
|
482
|
+
end
|
483
|
+
|
484
|
+
# Gets the WHOIS contact details for a domain
|
485
|
+
#
|
486
|
+
# The returned hash has the following keys
|
487
|
+
# - (Hash<Symbol,Registrant>) +:contacts+ -- Registrant object
|
488
|
+
# - (Time) +:updated_date+ -- Domain last update time
|
489
|
+
# - (Time) +:created_date+ -- Domain creation date
|
490
|
+
# - (Time) +:expiry_date+ -- Domain expiration date
|
491
|
+
# - (Array) +:nameservers+ -- Array of name server names
|
492
|
+
# - (String) +:status+ -- Registrar lock status
|
493
|
+
#
|
494
|
+
# @param [String] domain Domain name to retrieve WHOIS information for
|
495
|
+
# @return [Hash] response data
|
496
|
+
def get_whois_contact(domain)
|
497
|
+
xml = send_recv(:GetWhoisContact, split_domain(domain))
|
498
|
+
|
499
|
+
out = {:contacts => {}}
|
500
|
+
xml.GetWhoisContacts do
|
501
|
+
xml.contacts.contact do |contact,_|
|
502
|
+
case contact['ContactType']
|
503
|
+
when 'Registrant'
|
504
|
+
out[:contacts][:registrant] = Registrant.from_xml(contact)
|
505
|
+
when 'Administrative'
|
506
|
+
out[:contacts][:administrative] = Registrant.from_xml(contact)
|
507
|
+
when 'Technical'
|
508
|
+
out[:contacts][:technical] = Registrant.from_xml(contact)
|
509
|
+
when 'Billing'
|
510
|
+
out[:contacts][:billing] = Registrant.from_xml(contact)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
xml.send("rrp-info") do
|
515
|
+
out[:updated_date] = Time.parse(xml.send("updated-date"))
|
516
|
+
out[:created_date] = Time.parse(xml.send("created-date"))
|
517
|
+
out[:expiry_date] = Time.parse(xml.send("registration-expiration-date"))
|
518
|
+
out[:nameservers] = Array.new
|
519
|
+
xml.nameserver do
|
520
|
+
xml.nameserver do
|
521
|
+
out[:nameservers] << xml.to_s.downcase
|
522
|
+
end
|
523
|
+
end
|
524
|
+
xml.status do
|
525
|
+
out[:status] = xml.status # != registration status from get_domain_info. = lock status
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
out
|
531
|
+
end
|
532
|
+
|
533
|
+
def get_contacts(domain)
|
534
|
+
xml = send_recv(:GetContacts, split_domain(domain))
|
535
|
+
xml = xml.GetContacts
|
536
|
+
|
537
|
+
out = {}
|
538
|
+
out[:registrant] = Registrant.from_xml(xml.Registrant)
|
539
|
+
out[:aux_billing] = Registrant.from_xml(xml.AuxBilling)
|
540
|
+
out[:administrative] = Registrant.from_xml(xml.Admin)
|
541
|
+
out[:technical] = Registrant.from_xml(xml.Tech)
|
542
|
+
out[:billing] = Registrant.from_xml(xml.Billing)
|
543
|
+
out
|
544
|
+
end
|
545
|
+
|
546
|
+
# Push a domain to another eNom account.
|
547
|
+
#
|
548
|
+
# This is much like a domain transfer except it is wholly within the scope
|
549
|
+
# of eNoms registration system.
|
550
|
+
#
|
551
|
+
# @param [String] domain Domain name to push
|
552
|
+
# @param [String] account_id eNom Account ID to push the domain to
|
553
|
+
# @param [Boolean] push_contact Should push the domain contact information
|
554
|
+
# @return [Boolean] whether the push was successful or not
|
555
|
+
def push_domain(domain, account_id, push_contact = true)
|
556
|
+
xml = send_recv(:PushDomain, split_domain(domain).merge(
|
557
|
+
:AccountID => account_id, :PushContact => (push_contact ? 1 : 0)))
|
558
|
+
xml.PushDomain?
|
559
|
+
end
|
560
|
+
|
561
|
+
# Register a domain name server
|
562
|
+
#
|
563
|
+
# @param [String] nameserver Nameserver to register
|
564
|
+
# @param [String] ip IPv4 Address of the nameserver
|
565
|
+
# @return true if registration was successful
|
566
|
+
# @raise [ResponseError] if an error occurred
|
567
|
+
def register_nameserver(nameserver, ip)
|
568
|
+
send_recv(:RegisterNameServer, :Add => 'true', :NSName => nameserver, :IP => ip)
|
569
|
+
true # send_recv will raise a ResponseError if ErrCount > 0
|
570
|
+
end
|
571
|
+
|
572
|
+
# Sets the registrar lock on a domain name.
|
573
|
+
#
|
574
|
+
# @param [String] domain Domain to set the lock on
|
575
|
+
# @param [Boolean] new_state True to lock, False to unlock
|
576
|
+
# @return [false] if setting failed
|
577
|
+
# @return [String] lock status
|
578
|
+
def set_reg_lock(domain, new_state) # true to lock, false to unlock
|
579
|
+
xml = send_recv(:SetRegLock, split_domain(domain).merge(:UnlockRegistrar => (new_state ? '0' : '1')))
|
580
|
+
|
581
|
+
ret = xml.RegistrarLock.strip
|
582
|
+
return false if ret == 'Failed'
|
583
|
+
ret
|
584
|
+
end
|
585
|
+
|
586
|
+
# Reactivates an expired domain in real time.
|
587
|
+
#
|
588
|
+
# @param [String] domain Expired Domain name to register
|
589
|
+
# @param [Number] years Number of years to register the domain for
|
590
|
+
# @return [String] response status
|
591
|
+
def update_expired_domains(domain, years) # Like :extend, but for expired domains
|
592
|
+
xml = send_recv(:UpdateExpiredDomains, :DomainName => domain, :NumYears => years.to_i)
|
593
|
+
xml.Status.strip
|
594
|
+
end
|
595
|
+
|
596
|
+
# Change the IP address of a registered name server.
|
597
|
+
#
|
598
|
+
# @param [String] nameserver Nameserver to update the IP address of
|
599
|
+
# @param [String] old_ip Old IPv4 address of the nameserver
|
600
|
+
# @param [String] new_ip New IPv4 address of the nameserver
|
601
|
+
# @return [Boolean] success or failure of the update
|
602
|
+
def update_nameserver(nameserver, old_ip, new_ip)
|
603
|
+
xml = send_recv(:RegisterNameServer, :NS => nameserver, :OldIP => old_ip, :NewIP => new_ip)
|
604
|
+
xml.NSSuccess?
|
605
|
+
end
|
606
|
+
|
607
|
+
# Modify the name servers for a domain.
|
608
|
+
#
|
609
|
+
# @param [String] domain Domain name to set the nameserver of
|
610
|
+
# @param [String, ...] nameservers Nameservers to set for the domain
|
611
|
+
# @raise [RuntimeError] if number of nameservers exceeds 12
|
612
|
+
def modify_ns(domain, *nameservers)
|
613
|
+
raise "Maximum nameserver limit is 12" if nameservers.size > 12
|
614
|
+
xml = send_recv(:ModifyNS, split_domain(domain)) do |d|
|
615
|
+
if nameservers.empty?
|
616
|
+
d['NS1'] = ''
|
617
|
+
else
|
618
|
+
nameservers.each_with_index do |n,i|
|
619
|
+
d["NS#{i}"] = n
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
xml.RRPCode == '200'
|
624
|
+
end
|
625
|
+
|
626
|
+
# Gets information about a domain
|
627
|
+
#
|
628
|
+
# The returned hash contains the following keys
|
629
|
+
# - (BOOL) +:known+ -- Whether the domain is known to eNom
|
630
|
+
# - (BOOL) +:in_account+ -- Whether the domain belongs to the account
|
631
|
+
# - (String) +:last_order_id+ -- Last Order ID for the domain if domain belongs to the account
|
632
|
+
#
|
633
|
+
# @param [String] domain Domain to check the status of
|
634
|
+
# @param [String] type Order Type to query with, one of 'Purchase', 'Transfer' or 'Extend'
|
635
|
+
# @return [Hash] of information about the domain
|
636
|
+
def status_domain(domain, type = 'Purchase')
|
637
|
+
raise ArgumentError, "type must be Purchase, Transfer or Extend" unless %w(purchase transfer extend).include?(type.downcase)
|
638
|
+
begin
|
639
|
+
xml = send_recv(:StatusDomain, split_domain(domain).merge(:OrderType => type))
|
640
|
+
|
641
|
+
xml.DomainStatus do
|
642
|
+
return { :known => (xml.Known == 'Known'),
|
643
|
+
:in_account => xml.InAccount?, # It'll be either 0 or 1 here, case 2 raises an exception
|
644
|
+
:last_order_id => xml.OrderID }
|
645
|
+
end
|
646
|
+
rescue ResponseError => e
|
647
|
+
if e.messages.include?("The domain does not belong to this account")
|
648
|
+
# It returns an error if the domain is known to eNom but in another account
|
649
|
+
return { :known => true, :in_account => false }
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
# Purchase a domain name.
|
655
|
+
#
|
656
|
+
# The returned hash has the following keys
|
657
|
+
# - (Symbol) +:result+ -- Either +:registered+ or +:ordered+. The latter if the TLD does not support real time registrations.
|
658
|
+
# - (String) +:order_id+ -- Order ID of the purchase
|
659
|
+
#
|
660
|
+
# @param [String] domain Domain name to register
|
661
|
+
# @param [Registrant] registrant Registrant of the domain
|
662
|
+
# @param [Array<String>, nil] nameservers Nameservers to set for the domain, nil sends blank NS1
|
663
|
+
# @param [Hash] options Options to configure the registration
|
664
|
+
# @option options [Integer] :period Number of years to register the domain for
|
665
|
+
# @return [Hash] :result => :registered and :order_id if successful
|
666
|
+
# @return [Hash] :result => :ordered and :order_id if not a Real Time TLD
|
667
|
+
# @raise [RuntimeError] if more than 12 nameservers are passed
|
668
|
+
# @raise [ResponseError] if regisration failed
|
669
|
+
def purchase(domain, registrant, nameservers, options = {})
|
670
|
+
raise "Maximum nameserver limit is 12" if nameservers.size > 12
|
671
|
+
opts = registrant.to_post_data('Registrant')
|
672
|
+
opts[:NumYears] = options.delete(:period) if options.has_key?(:period)
|
673
|
+
|
674
|
+
xml = send_recv(:Purchase, split_domain(domain).merge(opts)) do |d|
|
675
|
+
if nameservers.empty?
|
676
|
+
d['NS1'] = ''
|
677
|
+
else
|
678
|
+
nameservers.each_with_index do |n,i|
|
679
|
+
d["NS#{i}"] = n
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
case xml.RRPCode.to_i
|
685
|
+
when 200
|
686
|
+
return { :result => :registered, :order_id => xml.OrderID }
|
687
|
+
when 1300
|
688
|
+
raise ResponseError.new([xml.RRPText]) if xml.IsRealTimeTLD?
|
689
|
+
return { :result => :ordered, :order_id => xml.OrderID }
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
private
|
694
|
+
# Split the domain into Top level and Domain components
|
695
|
+
#
|
696
|
+
# @param [String] domain Domain name to split
|
697
|
+
# @return [Hash] with :SLD and :TLD parts
|
698
|
+
def split_domain(domain)
|
699
|
+
s, t = domain.split('.', 2)
|
700
|
+
{:SLD => s, :TLD => t}
|
701
|
+
end
|
702
|
+
|
703
|
+
# Sends the payload
|
704
|
+
#
|
705
|
+
# @param [String] method API command name
|
706
|
+
# @param [Hash] post_data Hash of POST data
|
707
|
+
# @yield [post_data] Block to append additional post data
|
708
|
+
# @yieldparam [Hash] post_data Hash of POST data
|
709
|
+
# @raise [ResponseError] if any errors are returned from the command
|
710
|
+
# @raise [IncompleteResponseError] if the response does not indicate Done
|
711
|
+
def send_recv(method, post_data = {}, &block)
|
712
|
+
yield post_data if block
|
713
|
+
@response = @conn.send(method, post_data)
|
714
|
+
xml = Nokogiri::XML.parse(@response)
|
715
|
+
|
716
|
+
if (err_count = xml.xpath('//ErrCount').first.content.strip.to_i) > 0
|
717
|
+
errs = (1..err_count).map { |i| xml.xpath("//Err#{i}").first.content.strip }
|
718
|
+
raise ResponseError.new(errs)
|
719
|
+
end
|
720
|
+
|
721
|
+
unless xml.xpath('//Done').first.content.strip =~ /true/i
|
722
|
+
raise IncompleteResponseError.new(xml)
|
723
|
+
end
|
724
|
+
|
725
|
+
demolisher = Demolisher.demolish(xml)
|
726
|
+
demolisher.send("interface-response")
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module EnomAPI
|
4
|
+
# Interface proxy for the eNom Reseller API
|
5
|
+
class Interface
|
6
|
+
# Version of the Interface class, sent with the HTTP requests
|
7
|
+
VERSION = '0.1.0'
|
8
|
+
|
9
|
+
# @param [String] user eNom Account Login ID
|
10
|
+
# @param [String] passwd eNom Account Password
|
11
|
+
# @param [String] server Server to connect to
|
12
|
+
def initialize(user, passwd, server = 'reseller.enom.com')
|
13
|
+
@user, @passwd = user, passwd
|
14
|
+
@uri = URI.parse('https://%s/interface.asp' % server)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @yield [q] Search query block
|
18
|
+
# @yieldparam [SearchQuery] q SearchQuery instance
|
19
|
+
# @return [String] XML Body of the Search Query results
|
20
|
+
def search
|
21
|
+
q = yield SearchQuery.new
|
22
|
+
send_request(q.to_post_data)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Symbol] meth API command to execute
|
26
|
+
# @param [Hash] options POST data to send to the API
|
27
|
+
# @return [String] XML Body of the response
|
28
|
+
def method_missing(meth, options = {})
|
29
|
+
send_request(options.merge(:command => meth.to_s, :responseType => 'xml'))
|
30
|
+
end
|
31
|
+
private
|
32
|
+
# @param [Hash] data POST data to send to interface.asp
|
33
|
+
# @param [Integer] attempts Number of attempts to try, default 3
|
34
|
+
# @return [String] XML Body of the response
|
35
|
+
def send_request(data, attempts = 3)
|
36
|
+
begin
|
37
|
+
s_client = Net::HTTP.new(@uri.host, @uri.port)
|
38
|
+
s_client.use_ssl = true
|
39
|
+
s_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
40
|
+
|
41
|
+
@response = s_client.start do |https|
|
42
|
+
@request = Net::HTTP::Post.new(@uri.path)
|
43
|
+
@request.add_field('User-Agent', "Ruby eNom API Client v#{VERSION}")
|
44
|
+
@request.content_type = 'application/x-www-form-urlencoded'
|
45
|
+
|
46
|
+
@request.body = data.merge(:uid => @user, :pw => @passwd).map { |k,v|
|
47
|
+
"#{@request.send(:urlencode, k.to_s)}=#{@request.send(:urlencode, v.to_s)}" }.join("&")
|
48
|
+
|
49
|
+
https.request(@request)
|
50
|
+
end
|
51
|
+
|
52
|
+
if @response.kind_of?(Net::HTTPSuccess)
|
53
|
+
@response.body
|
54
|
+
else
|
55
|
+
raise @response
|
56
|
+
end
|
57
|
+
rescue ::Timeout::Error => e
|
58
|
+
if attempts == 1
|
59
|
+
raise e
|
60
|
+
else
|
61
|
+
send_request(data, attempts - 1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'demolisher'
|
2
|
+
|
3
|
+
module EnomAPI
|
4
|
+
# Represents a registrant or other type of Contact in the eNom API
|
5
|
+
class Registrant
|
6
|
+
# @param [Demolisher, String] xmldoc Demolisher or XML String of registrant information
|
7
|
+
# @return [Registrant] Registrant composed from the information in the xmldoc
|
8
|
+
def self.from_xml(xmldoc)
|
9
|
+
@xml = xml = xmldoc.kind_of?(Demolisher::Node) ? xmldoc : Demolisher.demolish(xmldoc.to_s)
|
10
|
+
r = new("", "")
|
11
|
+
|
12
|
+
mapping = if xml.FName
|
13
|
+
from_xml_mapping_one
|
14
|
+
elsif xml.RegistrantFirstName
|
15
|
+
from_xml_mapping_two('Registrant')
|
16
|
+
elsif xml.AuxBillingFirstName
|
17
|
+
from_xml_mapping_two('AuxBilling')
|
18
|
+
elsif xml.TechFirstName
|
19
|
+
from_xml_mapping_two('Tech')
|
20
|
+
elsif xml.AdminFirstName
|
21
|
+
from_xml_mapping_two('Admin')
|
22
|
+
elsif xml.BillingFirstName
|
23
|
+
from_xml_mapping_two('Billing')
|
24
|
+
end
|
25
|
+
|
26
|
+
mapping.each do |meth, el|
|
27
|
+
case el
|
28
|
+
when Array
|
29
|
+
r.send(:"#{meth}=", el.map { |n| xml.send(n).to_s }.delete_if { |n| n.nil? || n == "" }.join("\n"))
|
30
|
+
else
|
31
|
+
r.send(:"#{meth}=", xml.send(el).to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
r
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :id, :firstname, :lastname, :phone, :phone_extension, :fax, :email,
|
39
|
+
:organisation, :job_title, :address, :city, :state, :postal_code, :country
|
40
|
+
|
41
|
+
# @param [String] first Registrant first name
|
42
|
+
# @param [String] last Registrant last name
|
43
|
+
# @yield block to configure the registrant, any method on Registrant may be called.
|
44
|
+
def initialize(first, last, &blk)
|
45
|
+
raise ArgumentError, "first and last may not be nil" unless first && last
|
46
|
+
|
47
|
+
@firstname, @lastname = first, last
|
48
|
+
instance_eval(&blk) if blk
|
49
|
+
end
|
50
|
+
|
51
|
+
# Converts the object into a form suitable for POSTing to the eNom API
|
52
|
+
#
|
53
|
+
# @param [String] prefix String to prepend to key names
|
54
|
+
# @return [Hash] Hash of data for the registrant
|
55
|
+
def to_post_data(prefix=nil)
|
56
|
+
data = {
|
57
|
+
"#{prefix}FirstName" => firstname,
|
58
|
+
"#{prefix}LastName" => lastname,
|
59
|
+
"#{prefix}City" => city,
|
60
|
+
"#{prefix}StateProvince" => state,
|
61
|
+
"#{prefix}PostalCode" => postal_code,
|
62
|
+
"#{prefix}Country" => country,
|
63
|
+
"#{prefix}EmailAddress" => email,
|
64
|
+
"#{prefix}Phone" => phone,
|
65
|
+
"#{prefix}Fax" => fax }
|
66
|
+
data["#{prefix}Address1"], data["#{prefix}Address2"] = address.split("\n", 2)
|
67
|
+
|
68
|
+
unless organisation.nil? || organisation == ''
|
69
|
+
data["#{prefix}OrganizationName"] = organisation
|
70
|
+
data["#{prefix}JobTitle"] = (job_title || "Domains Manager")
|
71
|
+
end
|
72
|
+
data
|
73
|
+
end
|
74
|
+
private
|
75
|
+
def self.from_xml_mapping_one
|
76
|
+
{ :firstname => :FName, :lastname => :LName,
|
77
|
+
:organisation => :Organization, :job_title => :JobTitle,
|
78
|
+
:city => :City, :state => :StateProvince, :postal_code => :PostalCode, :country => :Country,
|
79
|
+
:phone => :Phone, :phone_extension => :PhoneExt, :fax => :Fax, :email => :EmailAddress,
|
80
|
+
:address => [:Address1, :Address2] }
|
81
|
+
end
|
82
|
+
def self.from_xml_mapping_two(p = nil)
|
83
|
+
{ :id => "#{p}PartyID",
|
84
|
+
:firstname => "#{p}FirstName", :lastname => "#{p}LastName",
|
85
|
+
:organisation => "#{p}OrganizationName", :job_title => "#{p}JobTitle",
|
86
|
+
:city => "#{p}City", :state => "#{p}StateProvince", :postal_code => "#{p}PostalCode", :country => "#{p}Country",
|
87
|
+
:phone => "#{p}Phone", :phone_extension => "#{p}PhoneExt", :fax => "#{p}Fax", :email => "#{p}EmailAddress",
|
88
|
+
:address => ["#{p}Address1", "#{p}Address2"] }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module EnomAPI
|
2
|
+
# Class to define the parameters of an eNom search query
|
3
|
+
class SearchQuery
|
4
|
+
def initialize
|
5
|
+
@options = {'responsetype' => 'xml', 'command' => 'advanceddomainsearch'}
|
6
|
+
end
|
7
|
+
|
8
|
+
# Change the ordering of the query results.
|
9
|
+
#
|
10
|
+
# @param [String] opt Ordering option, one of sld, tld, nsstatus, expdate, or renew
|
11
|
+
def order_by(opt)
|
12
|
+
raise ArgumentError, "invalid order by value" unless %w(sld tld nsstatus expdate renew).include?(opt)
|
13
|
+
@options['orderby'] = opt
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Limit the quantity of results returned
|
18
|
+
#
|
19
|
+
# @overload limit(number)
|
20
|
+
# @param [Integer] number Number of records to return
|
21
|
+
# @overload limit(start, number)
|
22
|
+
# @param [Integer] start Start position in the results
|
23
|
+
# @param [Integer] number Number of records to return
|
24
|
+
def limit(num_or_start, num = nil)
|
25
|
+
if num.nil?
|
26
|
+
raise ArgumentError, "invalid limit" unless num_or_start.kind_of?(Integer)
|
27
|
+
@options['recordstoreturn'] = num_or_start
|
28
|
+
else
|
29
|
+
raise ArgumentError, "invalid limit start" unless num_or_start.kind_of?(Integer)
|
30
|
+
raise ArgumentError, "invalid limit size" unless num_or_start.kind_of?(Integer)
|
31
|
+
@options['recordstoreturn'] = num
|
32
|
+
@options['startposition'] = num_or_start
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Hash] conditions Search conditions
|
38
|
+
def where(conditions = {})
|
39
|
+
@options = conditions.merge(@options)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return POST data options
|
44
|
+
def to_post_data
|
45
|
+
if @options[:creationdate].respond_to?(:strftime)
|
46
|
+
@options[:creationdate] = @options[:creationdate].strftime("%m/%d/%Y")
|
47
|
+
end
|
48
|
+
@options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: enom-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Geoff Garside
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-29 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: thoughtbot-shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: yard
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: demolisher
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 7
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
- 6
|
61
|
+
- 0
|
62
|
+
version: 0.6.0
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
description: Client for communicating with the eNom API
|
66
|
+
email: geoff@geoffgarside.co.uk
|
67
|
+
executables: []
|
68
|
+
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files:
|
72
|
+
- LICENSE
|
73
|
+
- README.rdoc
|
74
|
+
files:
|
75
|
+
- .document
|
76
|
+
- LICENSE
|
77
|
+
- README.rdoc
|
78
|
+
- Rakefile
|
79
|
+
- VERSION
|
80
|
+
- enom-api.gemspec
|
81
|
+
- lib/enom-api.rb
|
82
|
+
- lib/enom-api/client.rb
|
83
|
+
- lib/enom-api/interface.rb
|
84
|
+
- lib/enom-api/registrant.rb
|
85
|
+
- lib/enom-api/search_query.rb
|
86
|
+
- test/helper.rb
|
87
|
+
- test/test_enom-api.rb
|
88
|
+
has_rdoc: true
|
89
|
+
homepage: http://github.com/geoffgarside/enom-api
|
90
|
+
licenses: []
|
91
|
+
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 3
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 1.6.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: eNom API Client
|
122
|
+
test_files:
|
123
|
+
- test/helper.rb
|
124
|
+
- test/test_enom-api.rb
|