diff --git a/domain.go b/domain.go index 08d24ef..06881c1 100644 --- a/domain.go +++ b/domain.go @@ -2,46 +2,54 @@ package namecheap import ( "errors" + "fmt" "net/url" "strconv" "strings" + "time" ) const ( - domainsGetList = "namecheap.domains.getList" - domainsGetInfo = "namecheap.domains.getInfo" - domainsCheck = "namecheap.domains.check" - domainsCreate = "namecheap.domains.create" - domainsTLDList = "namecheap.domains.getTldList" - domainsRenew = "namecheap.domains.renew" - domainsSetContacts = "namecheap.domains.setContacts" + domainsGetList = "namecheap.domains.getList" + domainsGetInfo = "namecheap.domains.getInfo" + domainsCheck = "namecheap.domains.check" + domainsCreate = "namecheap.domains.create" + domainsTLDList = "namecheap.domains.getTldList" + domainsRenew = "namecheap.domains.renew" + domainsSetContacts = "namecheap.domains.setContacts" + domainsGetRegistrarLock = "namecheap.domains.getRegistrarLock" + domainsSetRegistrarLock = "namecheap.domains.setRegistrarLock" ) // DomainGetListResult represents the data returned by 'domains.getList' type DomainGetListResult struct { - ID int `xml:"ID,attr"` - Name string `xml:"Name,attr"` - User string `xml:"User,attr"` - Created string `xml:"Created,attr"` - Expires string `xml:"Expires,attr"` - IsExpired bool `xml:"IsExpired,attr"` - IsLocked bool `xml:"IsLocked,attr"` - AutoRenew bool `xml:"AutoRenew,attr"` - WhoisGuard string `xml:"WhoisGuard,attr"` + ID int `xml:"ID,attr"` + Name string `xml:"Name,attr"` + User string `xml:"User,attr"` + Created time.Time `xml:"-"` + CreatedString string `xml:"Created,attr"` + Expires time.Time `xml:"-"` + ExpiresString string `xml:"Expires,attr"` + IsExpired bool `xml:"IsExpired,attr"` + IsLocked bool `xml:"IsLocked,attr"` + AutoRenew bool `xml:"AutoRenew,attr"` + WhoisGuard string `xml:"WhoisGuard,attr"` } // DomainInfo represents the data returned by 'domains.getInfo' type DomainInfo struct { - ID int `xml:"ID,attr"` - Name string `xml:"DomainName,attr"` - Owner string `xml:"OwnerName,attr"` - Created string `xml:"DomainDetails>CreatedDate"` - Expires string `xml:"DomainDetails>ExpiredDate"` - IsExpired bool `xml:"IsExpired,attr"` - IsLocked bool `xml:"IsLocked,attr"` - AutoRenew bool `xml:"AutoRenew,attr"` - DNSDetails DNSDetails `xml:"DnsDetails"` - Whoisguard Whoisguard `xml:"Whoisguard"` + ID int `xml:"ID,attr"` + Name string `xml:"DomainName,attr"` + Owner string `xml:"OwnerName,attr"` + Created time.Time `xml:"-"` + CreatedString string `xml:"DomainDetails>CreatedDate"` + Expires time.Time `xml:"-"` + ExpiresString string `xml:"DomainDetails>ExpiredDate"` + IsExpired bool `xml:"IsExpired,attr"` + IsLocked bool `xml:"IsLocked,attr"` + AutoRenew bool `xml:"AutoRenew,attr"` + DNSDetails DNSDetails `xml:"DnsDetails"` + Whoisguard Whoisguard `xml:"Whoisguard"` } type DNSDetails struct { @@ -50,11 +58,19 @@ type DNSDetails struct { Nameservers []string `xml:"Nameserver"` } +type WhoisguardEmailDetails struct { + Email string `xml:"WhoisGuardEmail,attr"` + ForwardedTo string `xml:"ForwardedTo,attr"` + LastAutoEmailChangeDate string `xml:"LastAutoEmailChangeDate,attr"` + AutoEmailChangeFrequencyDays int `xml:"AutoEmailChangeFrequencyDays,attr"` +} type Whoisguard struct { - RawEnabled string `xml:"Enabled,attr"` - Enabled bool `xml:"-"` - ID int64 `xml:"ID"` - ExpiredDate string `xml:"ExpiredDate"` + RawEnabled string `xml:"Enabled,attr"` + Enabled bool `xml:"-"` + ID int64 `xml:"ID"` + ExpiredDate time.Time `xml:"-"` + ExpiredDateString string `xml:"ExpiredDate"` + EmailDetails WhoisguardEmailDetails `xml:"EmailDetails"` } type DomainCheckResult struct { @@ -84,13 +100,14 @@ type DomainCreateResult struct { } type DomainRenewResult struct { - DomainID int `xml:"DomainID,attr"` - Name string `xml:"DomainName,attr"` - Renewed bool `xml:"Renew,attr"` - ChargedAmount float64 `xml:"ChargedAmount,attr"` - OrderID int `xml:"OrderID,attr"` - TransactionID int `xml:"TransactionID,attr"` - ExpireDate string `xml:"DomainDetails>ExpiredDate"` + DomainID int `xml:"DomainID,attr"` + Name string `xml:"DomainName,attr"` + Renewed bool `xml:"Renew,attr"` + ChargedAmount float64 `xml:"ChargedAmount,attr"` + OrderID int `xml:"OrderID,attr"` + TransactionID int `xml:"TransactionID,attr"` + ExpireDate time.Time + ExpireDateString string `xml:"DomainDetails>ExpiredDate"` } type DomainSetContactsResult struct { @@ -98,6 +115,15 @@ type DomainSetContactsResult struct { IsSuccess bool `xml:"IsSuccess,attr"` } +type DomainRegistrarLockStatusResult struct { + Name string `xml:"Domain,attr"` + IsLocked bool `xml:"RegistrarLockStatus,attr"` +} + +type DomainSetRegistrarLockResult struct { + Name string `xml:"Domain,attr"` + IsSuccess bool `xml:"IsSuccess,attr"` +} type DomainCreateOption struct { AddFreeWhoisguard bool WGEnabled bool @@ -111,11 +137,33 @@ func (client *Client) DomainsGetList() ([]DomainGetListResult, error) { params: url.Values{}, } + requestInfo.params.Set("PageSize", "100") + resp, err := client.do(requestInfo) if err != nil { return nil, err } + for _, domain := range resp.Domains { + if domain.CreatedString != "" { + created, createdErr := resp.ParseDate(domain.CreatedString) + if createdErr != nil { + return nil, fmt.Errorf("error when parsing Created date to time.Time: %w", createdErr) + } + + domain.Created = created + } + + if domain.ExpiresString != "" { + expires, expiresErr := resp.ParseDate(domain.ExpiresString) + if expiresErr != nil { + return nil, fmt.Errorf("error when parsing Expires date to time.Time: %w", expiresErr) + } + + domain.Expires = expires + } + } + return resp.Domains, nil } @@ -133,9 +181,40 @@ func (client *Client) DomainGetInfo(domainName string) (*DomainInfo, error) { return nil, err } - if resp.DomainInfo != nil && strings.EqualFold(resp.DomainInfo.Whoisguard.RawEnabled, "true") { - resp.DomainInfo.Whoisguard.Enabled = true + if resp.DomainInfo != nil { + if strings.EqualFold(resp.DomainInfo.Whoisguard.RawEnabled, "true") { + resp.DomainInfo.Whoisguard.Enabled = true + } + + if resp.DomainInfo.CreatedString != "" { + created, createdErr := resp.ParseDate(resp.DomainInfo.CreatedString) + if createdErr != nil { + return nil, fmt.Errorf("error when parsing domain Created date to time.Time: %w", createdErr) + } + + resp.DomainInfo.Created = created + } + + if resp.DomainInfo.ExpiresString != "" { + expires, expiresErr := resp.ParseDate(resp.DomainInfo.ExpiresString) + if expiresErr != nil { + return nil, fmt.Errorf("error when parsing domain Expires date to time.Time: %w", expiresErr) + } + + resp.DomainInfo.Expires = expires + } + + if resp.DomainInfo.Whoisguard.ExpiredDateString != "" { + expired, expiredErr := resp.ParseDate(resp.DomainInfo.Whoisguard.ExpiredDateString) + if expiredErr != nil { + return nil, fmt.Errorf("error when parsing WhoisGuard Expired date to time.Time: %w", expiredErr) + } + + resp.DomainInfo.Whoisguard.ExpiredDate = expired + } + } + return resp.DomainInfo, nil } @@ -220,6 +299,15 @@ func (client *Client) DomainRenew(domainName string, years int) (*DomainRenewRes return nil, err } + if resp.DomainRenew.ExpireDateString != "" { + expired, expiredErr := resp.ParseDate(resp.DomainRenew.ExpireDateString) + if expiredErr != nil { + return nil, fmt.Errorf("error when parsing Expired date to time.Time: %w", expiredErr) + } + + resp.DomainRenew.ExpireDate = expired + } + return resp.DomainRenew, nil } @@ -241,3 +329,38 @@ func (client *Client) DomainSetContacts(domainName string) (*DomainSetContactsRe return resp.DomainSetContacts, nil } + +func (client *Client) DomainGetRegistrarLock(domainName string) (*DomainRegistrarLockStatusResult, error) { + requestInfo := &ApiRequest{ + command: domainsGetRegistrarLock, + method: "POST", + params: url.Values{}, + } + requestInfo.params.Set("DomainName", domainName) + + resp, err := client.do(requestInfo) + if err != nil { + return nil, err + } + + return resp.DomainRegistrarLockStatus, nil +} + +func (client *Client) DomainSetRegistrarLock(domainName string, lock bool) (*DomainSetRegistrarLockResult, error) { + requestInfo := &ApiRequest{ + command: domainsSetRegistrarLock, + method: "POST", + params: url.Values{}, + } + requestInfo.params.Set("DomainName", domainName) + if !lock { + requestInfo.params.Set("LockAction", "UNLOCK") + } + + resp, err := client.do(requestInfo) + if err != nil { + return nil, err + } + + return resp.DomainSetRegistrarLock, nil +} diff --git a/namecheap.go b/namecheap.go index 99851a3..adda38e 100644 --- a/namecheap.go +++ b/namecheap.go @@ -10,8 +10,10 @@ import ( "io/ioutil" "net/http" "net/url" + "regexp" "strconv" "strings" + "time" ) const defaultBaseURL = "https://api.namecheap.com/xml.response" @@ -38,29 +40,76 @@ type ApiRequest struct { params url.Values } +type NamecheapTimeLocation struct { + *time.Location +} + +// could be like --4:00, +5, +5:30, +2:50 +var gmtOffsetRegex = regexp.MustCompile(`([\-+])(\d+):?(\d+)?`) + +func (n *NamecheapTimeLocation) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var offsetStr string + if decodeErr := d.DecodeElement(&offsetStr, &start); decodeErr != nil { + return decodeErr + } + + pieces := gmtOffsetRegex.FindStringSubmatch(offsetStr) + + if len(pieces) == 0 { + return fmt.Errorf("GMTTimeDifference was not of expected format") + } + + sign := pieces[1] + hours := pieces[2] + minutes := pieces[3] + + if minutes == "" { + minutes = "00" + } + + dur, durErr := time.ParseDuration(fmt.Sprintf("%s%sh%sm", sign, hours, minutes)) + if durErr != nil { + return fmt.Errorf("error when converting parsed GMTTimeDifference to time.Duration: %w", durErr) + } + + n.Location = time.FixedZone("Namecheap", int(dur.Seconds())) + return nil +} + type ApiResponse struct { - Status string `xml:"Status,attr"` - Command string `xml:"RequestedCommand"` - TLDList []TLDListResult `xml:"CommandResponse>Tlds>Tld"` - Domains []DomainGetListResult `xml:"CommandResponse>DomainGetListResult>Domain"` - DomainInfo *DomainInfo `xml:"CommandResponse>DomainGetInfoResult"` - DomainDNSHosts *DomainDNSGetHostsResult `xml:"CommandResponse>DomainDNSGetHostsResult"` - DomainDNSSetHosts *DomainDNSSetHostsResult `xml:"CommandResponse>DomainDNSSetHostsResult"` - DomainCreate *DomainCreateResult `xml:"CommandResponse>DomainCreateResult"` - DomainRenew *DomainRenewResult `xml:"CommandResponse>DomainRenewResult"` - DomainsCheck []DomainCheckResult `xml:"CommandResponse>DomainCheckResult"` - DomainNSInfo *DomainNSInfoResult `xml:"CommandResponse>DomainNSInfoResult"` - DomainDNSSetCustom *DomainDNSSetCustomResult `xml:"CommandResponse>DomainDNSSetCustomResult"` - DomainSetContacts *DomainSetContactsResult `xml:"CommandResponse>DomainSetContactResult"` - SslActivate *SslActivateResult `xml:"CommandResponse>SSLActivateResult"` - SslCreate *SslCreateResult `xml:"CommandResponse>SSLCreateResult"` - SslCertificates []SslGetListResult `xml:"CommandResponse>SSLListResult>SSL"` - UsersGetPricing []UsersGetPricingResult `xml:"CommandResponse>UserGetPricingResult>ProductType"` - WhoisguardList []WhoisguardGetListResult `xml:"CommandResponse>WhoisguardGetListResult>Whoisguard"` - WhoisguardEnable whoisguardEnableResult `xml:"CommandResponse>WhoisguardEnableResult"` - WhoisguardDisable whoisguardDisableResult `xml:"CommandResponse>WhoisguardDisableResult"` - WhoisguardRenew *WhoisguardRenewResult `xml:"CommandResponse>WhoisguardRenewResult"` - Errors ApiErrors `xml:"Errors>Error"` + Status string `xml:"Status,attr"` + Command string `xml:"RequestedCommand"` + TLDList []TLDListResult `xml:"CommandResponse>Tlds>Tld"` + Domains []DomainGetListResult `xml:"CommandResponse>DomainGetListResult>Domain"` + DomainInfo *DomainInfo `xml:"CommandResponse>DomainGetInfoResult"` + DomainDNSHosts *DomainDNSGetHostsResult `xml:"CommandResponse>DomainDNSGetHostsResult"` + DomainDNSSetHosts *DomainDNSSetHostsResult `xml:"CommandResponse>DomainDNSSetHostsResult"` + DomainCreate *DomainCreateResult `xml:"CommandResponse>DomainCreateResult"` + DomainRenew *DomainRenewResult `xml:"CommandResponse>DomainRenewResult"` + DomainsCheck []DomainCheckResult `xml:"CommandResponse>DomainCheckResult"` + DomainNSInfo *DomainNSInfoResult `xml:"CommandResponse>DomainNSInfoResult"` + DomainDNSSetCustom *DomainDNSSetCustomResult `xml:"CommandResponse>DomainDNSSetCustomResult"` + DomainSetContacts *DomainSetContactsResult `xml:"CommandResponse>DomainSetContactResult"` + DomainRegistrarLockStatus *DomainRegistrarLockStatusResult `xml:"CommandResponse>DomainGetRegistrarLockResult"` + DomainSetRegistrarLock *DomainSetRegistrarLockResult `xml:"CommandResponse>DomainSetRegistrarLockResult"` + SslActivate *SslActivateResult `xml:"CommandResponse>SSLActivateResult"` + SslCreate *SslCreateResult `xml:"CommandResponse>SSLCreateResult"` + SslCertificates []SslGetListResult `xml:"CommandResponse>SSLListResult>SSL"` + UsersGetPricing []UsersGetPricingResult `xml:"CommandResponse>UserGetPricingResult>ProductType"` + WhoisguardList []WhoisguardGetListResult `xml:"CommandResponse>WhoisguardGetListResult>Whoisguard"` + WhoisguardEnable whoisguardEnableResult `xml:"CommandResponse>WhoisguardEnableResult"` + WhoisguardDisable whoisguardDisableResult `xml:"CommandResponse>WhoisguardDisableResult"` + WhoisguardRenew *WhoisguardRenewResult `xml:"CommandResponse>WhoisguardRenewResult"` + Errors ApiErrors `xml:"Errors>Error"` + GMTTimeDifference NamecheapTimeLocation `xml:"GMTTimeDifference"` +} + +func (a *ApiResponse) ParseTime(layout, value string) (time.Time, error) { + return time.ParseInLocation(layout, value, a.GMTTimeDifference.Location) +} + +func (a *ApiResponse) ParseDate(value string) (time.Time, error) { + return time.ParseInLocation("01/02/2006", value, a.GMTTimeDifference.Location) } // ApiError is the format of the error returned in the api responses. diff --git a/whoisguard.go b/whoisguard.go index d67ea1f..4f1d1cd 100644 --- a/whoisguard.go +++ b/whoisguard.go @@ -46,6 +46,8 @@ func (client *Client) WhoisguardGetList() ([]WhoisguardGetListResult, error) { params: url.Values{}, } + requestInfo.params.Set("PageSize", "100") + resp, err := client.do(requestInfo) if err != nil { return nil, err