├── .github └── workflows │ ├── go-cross.yml │ └── main.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── account.go ├── contact.go ├── dnssec.go ├── domain.go ├── go.mod ├── go.sum ├── goinwx.go ├── nameserver.go └── response.go /.github/workflows/go-cross.yml: -------------------------------------------------------------------------------- 1 | name: Go Matrix 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: 9 | branches: 10 | - main 11 | - master 12 | 13 | jobs: 14 | 15 | cross: 16 | name: Go 17 | runs-on: ${{ matrix.os }} 18 | env: 19 | CGO_ENABLED: 0 20 | 21 | strategy: 22 | matrix: 23 | go-version: [ stable, oldstable ] 24 | os: [ubuntu-latest, macos-latest, windows-latest] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-go@v5 29 | with: 30 | go-version: ${{ matrix.go-version }} 31 | 32 | - name: Test 33 | run: go test -v -cover ./... 34 | 35 | - name: Build 36 | run: go build -v -ldflags "-s -w" -trimpath 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: 9 | branches: 10 | - main 11 | - master 12 | 13 | jobs: 14 | 15 | main: 16 | name: Main Process 17 | runs-on: ubuntu-latest 18 | env: 19 | GO_VERSION: stable 20 | GOLANGCI_LINT_VERSION: v2.0.2 21 | CGO_ENABLED: 0 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ env.GO_VERSION }} 28 | 29 | - name: Check and get dependencies 30 | run: | 31 | go mod tidy 32 | git diff --exit-code go.mod 33 | git diff --exit-code go.sum 34 | go mod download 35 | 36 | # https://golangci-lint.run/usage/install#other-ci 37 | - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} 38 | run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} 39 | 40 | - name: Make 41 | run: make 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | vendor 4 | dist 5 | builds 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | formatters: 4 | enable: 5 | - gci 6 | - gofumpt 7 | settings: 8 | gofumpt: 9 | extra-rules: true 10 | 11 | linters: 12 | default: all 13 | disable: 14 | - cyclop # duplicate of gocyclo 15 | - dupl 16 | - err113 17 | - errname # Require a breaking change. 18 | - exhaustive 19 | - exhaustruct 20 | - forcetypeassert 21 | - lll 22 | - mnd 23 | - nlreturn 24 | - noctx 25 | - paralleltest 26 | - prealloc 27 | - rowserrcheck # not relevant (SQL) 28 | - sqlclosecheck # not relevant (SQL) 29 | - testpackage 30 | - tparallel 31 | - wrapcheck 32 | 33 | settings: 34 | depguard: 35 | rules: 36 | main: 37 | deny: 38 | - pkg: github.com/instana/testify 39 | desc: not allowed 40 | - pkg: github.com/pkg/errors 41 | desc: Should be replaced by standard lib errors package 42 | funlen: 43 | lines: -1 44 | statements: 40 45 | goconst: 46 | min-len: 5 47 | min-occurrences: 3 48 | gocritic: 49 | disabled-checks: 50 | - sloppyReassign 51 | - rangeValCopy 52 | - octalLiteral 53 | - paramTypeCombine # already handle by gofumpt.extra-rules 54 | enabled-tags: 55 | - diagnostic 56 | - style 57 | - performance 58 | settings: 59 | hugeParam: 60 | sizeThreshold: 100 61 | gocyclo: 62 | min-complexity: 15 63 | godox: 64 | keywords: 65 | - FIXME 66 | govet: 67 | disable: 68 | - fieldalignment 69 | enable-all: true 70 | misspell: 71 | locale: US 72 | 73 | exclusions: 74 | warn-unused: true 75 | presets: 76 | - comments 77 | rules: 78 | - linters: 79 | - bodyclose 80 | - funlen 81 | path: .*_test.go 82 | 83 | issues: 84 | max-issues-per-linter: 0 85 | max-same-issues: 0 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew 4 | Copyright (c) 2018-2024 NRDCG authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default clean check test 2 | 3 | default: clean check test build 4 | 5 | test: clean 6 | go test -v -cover ./... 7 | 8 | clean: 9 | rm -f cover.out 10 | 11 | build: 12 | go build 13 | 14 | check: 15 | golangci-lint run 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # INWX Go API client 2 | 3 | [![Build Status](https://github.com/nrdcg/goinwx/workflows/Main/badge.svg?branch=master)](https://github.com/nrdcg/goinwx/actions) 4 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/nrdcg/goinwx)](https://pkg.go.dev/github.com/nrdcg/goinwx) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/nrdcg/goinwx)](https://goreportcard.com/report/github.com/nrdcg/goinwx) 6 | 7 | This go library implements some parts of the official INWX XML-RPC API. 8 | 9 | ## API 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "log" 16 | 17 | "github.com/nrdcg/goinwx" 18 | ) 19 | 20 | func main() { 21 | client := goinwx.NewClient("username", "password", &goinwx.ClientOptions{Sandbox: true}) 22 | 23 | _, err := client.Account.Login() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | defer func() { 29 | if err := client.Account.Logout(); err != nil { 30 | log.Printf("inwx: failed to logout: %v", err) 31 | } 32 | }() 33 | 34 | var request = &goinwx.NameserverRecordRequest{ 35 | Domain: "domain.com", 36 | Name: "foo.domain.com.", 37 | Type: "TXT", 38 | Content: "aaa", 39 | TTL: 300, 40 | } 41 | 42 | _, err = client.Nameservers.CreateRecord(request) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | ``` 48 | 49 | ### Using 2FA 50 | 51 | If it is desired to use 2FA without manual entering the TOTP every time, 52 | you must set the parameter `otp-key` to the secret that is shown during the setup of 2FA for you INWX account. 53 | Otherwise, you can skip `totp.GenerateCode` step and enter the verification code of the Google Authenticator app every time manually. 54 | 55 | The `otp-key` looks something like `EELTWFL55ESIHPTJAAHBCY7LXBZARUOJ`. 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "log" 62 | "time" 63 | 64 | "github.com/nrdcg/goinwx" 65 | "github.com/pquerna/otp/totp" 66 | ) 67 | 68 | func main() { 69 | client := goinwx.NewClient("username", "password", &goinwx.ClientOptions{Sandbox: true}) 70 | 71 | resp, err := client.Account.Login() 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | if resp.TFA != "GOOGLE-AUTH" { 77 | log.Fatal("unsupported 2 Factor Authentication") 78 | } 79 | 80 | tan, err := totp.GenerateCode("otp-key", time.Now()) 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | err = client.Account.Unlock(tan) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | defer func() { 91 | if err := client.Account.Logout(); err != nil { 92 | log.Printf("inwx: failed to logout: %v", err) 93 | } 94 | }() 95 | 96 | request := &goinwx.NameserverRecordRequest{ 97 | Domain: "domain.com", 98 | Name: "foo.domain.com.", 99 | Type: "TXT", 100 | Content: "aaa", 101 | TTL: 300, 102 | } 103 | 104 | _, err = client.Nameservers.CreateRecord(request) 105 | if err != nil { 106 | log.Fatal(err) 107 | } 108 | } 109 | ``` 110 | 111 | ## Supported Features 112 | 113 | Full API documentation can be found [here](https://www.inwx.de/en/help/apidoc). 114 | 115 | The following parts are implemented: 116 | 117 | * Account 118 | * Login 119 | * Logout 120 | * Lock 121 | * Unlock (with mobile TAN) 122 | * Domains 123 | * Check 124 | * Register 125 | * Delete 126 | * Info 127 | * GetPrices 128 | * List 129 | * Whois 130 | * Update 131 | * Nameservers 132 | * Check 133 | * Create 134 | * Info 135 | * List 136 | * CreateRecord 137 | * UpdateRecord 138 | * DeleteRecord 139 | * FindRecordById 140 | * Contacts 141 | * List 142 | * Info 143 | * Create 144 | * Update 145 | * Delete 146 | 147 | ## Contributions 148 | 149 | Your contributions are very appreciated. 150 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import "github.com/go-viper/mapstructure/v2" 4 | 5 | const ( 6 | methodAccountLogin = "account.login" 7 | methodAccountLogout = "account.logout" 8 | methodAccountLock = "account.lock" 9 | methodAccountUnlock = "account.unlock" 10 | ) 11 | 12 | // AccountService API access to Account. 13 | type AccountService service 14 | 15 | // Login Account login. 16 | func (s *AccountService) Login() (*LoginResponse, error) { 17 | req := s.client.NewRequest(methodAccountLogin, map[string]interface{}{ 18 | "user": s.client.username, 19 | "pass": s.client.password, 20 | }) 21 | 22 | resp, err := s.client.Do(req) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | var result LoginResponse 28 | 29 | err = mapstructure.Decode(resp, &result) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return &result, nil 35 | } 36 | 37 | // Logout Account logout. 38 | func (s *AccountService) Logout() error { 39 | req := s.client.NewRequest(methodAccountLogout, nil) 40 | 41 | _, err := s.client.Do(req) 42 | 43 | return err 44 | } 45 | 46 | // Lock Account lock. 47 | func (s *AccountService) Lock() error { 48 | req := s.client.NewRequest(methodAccountLock, nil) 49 | 50 | _, err := s.client.Do(req) 51 | 52 | return err 53 | } 54 | 55 | // Unlock Account unlock. 56 | func (s *AccountService) Unlock(tan string) error { 57 | req := s.client.NewRequest(methodAccountUnlock, map[string]interface{}{ 58 | "tan": tan, 59 | }) 60 | 61 | _, err := s.client.Do(req) 62 | 63 | return err 64 | } 65 | 66 | // LoginResponse API model. 67 | type LoginResponse struct { 68 | CustomerID int64 `mapstructure:"customerId"` 69 | AccountID int64 `mapstructure:"accountId"` 70 | TFA string `mapstructure:"tfa"` 71 | BuildDate string `mapstructure:"builddate"` 72 | Version string `mapstructure:"version"` 73 | } 74 | -------------------------------------------------------------------------------- /contact.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import ( 4 | "github.com/fatih/structs" 5 | "github.com/go-viper/mapstructure/v2" 6 | ) 7 | 8 | const ( 9 | methodContactInfo = "contact.info" 10 | methodContactList = "contact.list" 11 | methodContactCreate = "contact.create" 12 | methodContactDelete = "contact.delete" 13 | methodContactUpdate = "contact.update" 14 | ) 15 | 16 | // ContactService API access to Contact. 17 | type ContactService service 18 | 19 | // Create Creates a contact. 20 | func (s *ContactService) Create(request *ContactCreateRequest) (int, error) { 21 | req := s.client.NewRequest(methodContactCreate, structs.Map(request)) 22 | 23 | resp, err := s.client.Do(req) 24 | if err != nil { 25 | return 0, err 26 | } 27 | 28 | result := make(map[string]int) 29 | 30 | err = mapstructure.Decode(resp, &result) 31 | if err != nil { 32 | return 0, err 33 | } 34 | 35 | return result["id"], nil 36 | } 37 | 38 | // Delete Deletes a contact. 39 | func (s *ContactService) Delete(roID int) error { 40 | req := s.client.NewRequest(methodContactDelete, map[string]interface{}{ 41 | "id": roID, 42 | }) 43 | 44 | _, err := s.client.Do(req) 45 | 46 | return err 47 | } 48 | 49 | // Update Updates a contact. 50 | func (s *ContactService) Update(request *ContactUpdateRequest) error { 51 | req := s.client.NewRequest(methodContactUpdate, structs.Map(request)) 52 | 53 | _, err := s.client.Do(req) 54 | 55 | return err 56 | } 57 | 58 | // Info Get information about a contact. 59 | func (s *ContactService) Info(contactID int) (*ContactInfoResponse, error) { 60 | requestMap := make(map[string]interface{}) 61 | requestMap["wide"] = 1 62 | 63 | if contactID != 0 { 64 | requestMap["id"] = contactID 65 | } 66 | 67 | req := s.client.NewRequest(methodContactInfo, requestMap) 68 | 69 | resp, err := s.client.Do(req) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | result := ContactInfoResponse{} 75 | 76 | err = mapstructure.Decode(resp, &result) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return &result, nil 82 | } 83 | 84 | // List Search contacts. 85 | func (s *ContactService) List(search string) (*ContactListResponse, error) { 86 | requestMap := make(map[string]interface{}) 87 | 88 | if search != "" { 89 | requestMap["search"] = search 90 | } 91 | 92 | req := s.client.NewRequest(methodContactList, requestMap) 93 | 94 | resp, err := s.client.Do(req) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | result := ContactListResponse{} 100 | 101 | err = mapstructure.Decode(resp, &result) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return &result, nil 107 | } 108 | 109 | // ContactCreateRequest API model. 110 | type ContactCreateRequest struct { 111 | Type string `structs:"type"` 112 | Name string `structs:"name"` 113 | Org string `structs:"org,omitempty"` 114 | Street string `structs:"street"` 115 | City string `structs:"city"` 116 | PostalCode string `structs:"pc"` 117 | StateProvince string `structs:"sp,omitempty"` 118 | CountryCode string `structs:"cc"` 119 | Voice string `structs:"voice"` 120 | Fax string `structs:"fax,omitempty"` 121 | Email string `structs:"email"` 122 | Remarks string `structs:"remarks,omitempty"` 123 | Protection bool `structs:"protection,omitempty"` 124 | Testing bool `structs:"testing,omitempty"` 125 | } 126 | 127 | // ContactUpdateRequest API model. 128 | type ContactUpdateRequest struct { 129 | ID int `structs:"id"` 130 | Name string `structs:"name,omitempty"` 131 | Org string `structs:"org,omitempty"` 132 | Street string `structs:"street,omitempty"` 133 | City string `structs:"city,omitempty"` 134 | PostalCode string `structs:"pc,omitempty"` 135 | StateProvince string `structs:"sp,omitempty"` 136 | CountryCode string `structs:"cc,omitempty"` 137 | Voice string `structs:"voice,omitempty"` 138 | Fax string `structs:"fax,omitempty"` 139 | Email string `structs:"email,omitempty"` 140 | Remarks string `structs:"remarks,omitempty"` 141 | Protection bool `structs:"protection,omitempty"` 142 | Testing bool `structs:"testing,omitempty"` 143 | } 144 | 145 | // ContactInfoResponse API model. 146 | type ContactInfoResponse struct { 147 | Contact Contact `mapstructure:"contact"` 148 | } 149 | 150 | // ContactListResponse API model. 151 | type ContactListResponse struct { 152 | Count int `mapstructure:"count"` 153 | Contacts []Contact `mapstructure:"contact"` 154 | } 155 | -------------------------------------------------------------------------------- /dnssec.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/fatih/structs" 8 | "github.com/go-viper/mapstructure/v2" 9 | ) 10 | 11 | const ( 12 | methodDNSSecAddDNSKey = "dnssec.adddnskey" 13 | methodDNSSecDeleteAll = "dnssec.deleteall" 14 | methodDNSSecDeleteDNSKey = "dnssec.deletednskey" 15 | methodDNSSecDisableDNSSec = "dnssec.disablednssec" 16 | methodDNSSecEnableDNSSec = "dnssec.enablednssec" 17 | methodDNSSecInfo = "dnssec.info" 18 | methodDNSSecListKeys = "dnssec.listkeys" 19 | ) 20 | 21 | // DNSSecService API access to DNSSEC. 22 | type DNSSecService service 23 | 24 | // Add adds one DNSKEY to a specified domain. 25 | func (s *DNSSecService) Add(request *DNSSecAddRequest) (*DNSSecAddResponse, error) { 26 | if request == nil { 27 | return nil, errors.New("request can't be nil") 28 | } 29 | 30 | requestMap := structs.Map(request) 31 | 32 | req := s.client.NewRequest(methodDNSSecAddDNSKey, requestMap) 33 | 34 | resp, err := s.client.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | var result DNSSecAddResponse 40 | 41 | err = mapstructure.Decode(resp, &result) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return &result, nil 47 | } 48 | 49 | // DeleteAll deletes all DNSKEY/DS entries for a domain. 50 | func (s *DNSSecService) DeleteAll(domain string) error { 51 | req := s.client.NewRequest(methodDNSSecDeleteAll, map[string]interface{}{ 52 | "domainName": domain, 53 | }) 54 | 55 | _, err := s.client.Do(req) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // DeleteDNSKey deletes one DNSKEY from a specified domain. 64 | func (s *DNSSecService) DeleteDNSKey(key string) error { 65 | req := s.client.NewRequest(methodDNSSecDeleteDNSKey, map[string]interface{}{ 66 | "key": key, 67 | }) 68 | 69 | _, err := s.client.Do(req) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // Disable disables automated DNSSEC management for a domain. 78 | func (s *DNSSecService) Disable(domain string) error { 79 | req := s.client.NewRequest(methodDNSSecDisableDNSSec, map[string]interface{}{ 80 | "domainName": domain, 81 | }) 82 | 83 | _, err := s.client.Do(req) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | 91 | // Enable enables automated DNSSEC management for a domain. 92 | func (s *DNSSecService) Enable(domain string) error { 93 | req := s.client.NewRequest(methodDNSSecEnableDNSSec, map[string]interface{}{ 94 | "domainName": domain, 95 | }) 96 | 97 | _, err := s.client.Do(req) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | return nil 103 | } 104 | 105 | // Info gets current DNSSEC information. 106 | func (s *DNSSecService) Info(domains []string) (*DNSSecInfoResponse, error) { 107 | req := s.client.NewRequest(methodDNSSecInfo, map[string]interface{}{ 108 | "domains": domains, 109 | }) 110 | 111 | resp, err := s.client.Do(req) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | var result DNSSecInfoResponse 117 | 118 | err = mapstructure.Decode(resp, &result) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | return &result, nil 124 | } 125 | 126 | // List lists domains. 127 | func (s *DNSSecService) List(request *DNSSecServiceListRequest) (*DNSSecServiceList, error) { 128 | if request == nil { 129 | return nil, errors.New("request can't be nil") 130 | } 131 | 132 | requestMap := structs.Map(request) 133 | 134 | req := s.client.NewRequest(methodDNSSecListKeys, requestMap) 135 | 136 | resp, err := s.client.Do(req) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | var result DNSSecServiceList 142 | 143 | err = mapstructure.Decode(resp, &result) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | return &result, nil 149 | } 150 | 151 | // DNSSecAddRequest API model. 152 | type DNSSecAddRequest struct { 153 | DomainName string `structs:"domainName,omitempty"` 154 | DNSKey string `structs:"dnskey,omitempty"` 155 | DS string `structs:"ds,omitempty"` 156 | CalculateDigest bool `structs:"calculateDigest,omitempty"` 157 | DigestType int `structs:"digestType,omitempty"` 158 | } 159 | 160 | // DNSSecAddResponse API model. 161 | type DNSSecAddResponse struct { 162 | DNSKey string `mapstructure:"dnskey"` 163 | DS string `mapstructure:"ds"` 164 | } 165 | 166 | // DNSSecInfoResponse API model. 167 | type DNSSecInfoResponse struct { 168 | Data []DNSSecInfo `mapstructure:"data"` 169 | } 170 | 171 | // DNSSecInfo API model. 172 | type DNSSecInfo struct { 173 | Domain string `mapstructure:"domain"` 174 | KeyCount int `mapstructure:"keyCount"` 175 | DNSSecStatus string `mapstructure:"dnsSecStatus"` 176 | } 177 | 178 | // DNSSecServiceListRequest API model. 179 | type DNSSecServiceListRequest struct { 180 | DomainName string `structs:"domainName,omitempty"` 181 | DomainNameIdn string `structs:"domainNameIdn,omitempty"` 182 | KeyTag int `structs:"keyTag,omitempty"` 183 | FlagID int `structs:"flagId,omitempty"` 184 | AlgorithmID int `structs:"algorithmId,omitempty"` 185 | PublicKey string `structs:"publicKey,omitempty"` 186 | DigestTypeID int `structs:"digestTypeId,omitempty"` 187 | Digest string `structs:"digest,omitempty"` 188 | CreatedBefore string `structs:"createdBefore,omitempty"` 189 | CreatedAfter string `structs:"createdAfter,omitempty"` 190 | Status string `structs:"status,omitempty"` 191 | Active int `structs:"active,omitempty"` 192 | Page int `structs:"page,omitempty"` 193 | PageLimit int `structs:"pagelimit,omitempty"` 194 | } 195 | 196 | // DNSSecServiceList API model. 197 | type DNSSecServiceList struct { 198 | DNSKeys []DNSSecServiceListResponse `mapstructure:"dnskey"` 199 | } 200 | 201 | // DNSSecServiceListResponse API model. 202 | type DNSSecServiceListResponse struct { 203 | OwnerName string `mapstructure:"ownerName"` 204 | ID int `mapstructure:"id"` 205 | DomainID int `mapstructure:"domainId"` 206 | KeyTag int `mapstructure:"keyTag"` 207 | FlagID int `mapstructure:"flagId"` 208 | AlgorithmID int `mapstructure:"algorithmId"` 209 | PublicKey string `mapstructure:"publicKey"` 210 | DigestTypeID int `mapstructure:"digestTypeId"` 211 | Digest string `mapstructure:"digest"` 212 | Created time.Time `mapstructure:"created"` 213 | Status string `mapstructure:"status"` 214 | Active int `mapstructure:"active"` 215 | } 216 | -------------------------------------------------------------------------------- /domain.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/fatih/structs" 9 | "github.com/go-viper/mapstructure/v2" 10 | ) 11 | 12 | const ( 13 | methodDomainCheck = "domain.check" 14 | methodDomainCreate = "domain.create" 15 | methodDomainDelete = "domain.delete" 16 | methodDomainGetPrices = "domain.getPrices" 17 | methodDomainGetRules = "domain.getRules" 18 | methodDomainInfo = "domain.info" 19 | methodDomainList = "domain.list" 20 | methodDomainLog = "domain.log" 21 | methodDomainPush = "domain.push" 22 | methodDomainRenew = "domain.renew" 23 | methodDomainRestore = "domain.restore" 24 | methodDomainStats = "domain.stats" 25 | methodDomainTrade = "domain.trade" 26 | methodDomainTransfer = "domain.transfer" 27 | methodDomainTransferOut = "domain.transferOut" 28 | methodDomainUpdate = "domain.update" 29 | methodDomainWhois = "domain.whois" 30 | ) 31 | 32 | // DomainService API access to Domain. 33 | type DomainService service 34 | 35 | // Check checks domains. 36 | func (s *DomainService) Check(domains []string) ([]DomainCheckResponse, error) { 37 | req := s.client.NewRequest(methodDomainCheck, map[string]interface{}{ 38 | "domain": domains, 39 | "wide": "2", 40 | }) 41 | 42 | resp, err := s.client.Do(req) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | root := new(domainCheckResponseRoot) 48 | 49 | err = mapstructure.Decode(resp, &root) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return root.Domains, nil 55 | } 56 | 57 | // GetPrices gets TLDS prices. 58 | func (s *DomainService) GetPrices(tlds []string) ([]DomainPriceResponse, error) { 59 | req := s.client.NewRequest(methodDomainGetPrices, map[string]interface{}{ 60 | "tld": tlds, 61 | "vat": false, 62 | }) 63 | 64 | resp, err := s.client.Do(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | root := new(domainPriceResponseRoot) 70 | 71 | err = mapstructure.Decode(resp, &root) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | return root.Prices, nil 77 | } 78 | 79 | // Register registers a domain. 80 | func (s *DomainService) Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error) { 81 | req := s.client.NewRequest(methodDomainCreate, structs.Map(request)) 82 | 83 | resp, err := s.client.Do(req) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | var result DomainRegisterResponse 89 | 90 | err = mapstructure.Decode(resp, &result) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return &result, nil 96 | } 97 | 98 | // Delete deletes a domain. 99 | func (s *DomainService) Delete(domain string, scheduledDate time.Time) error { 100 | req := s.client.NewRequest(methodDomainDelete, map[string]interface{}{ 101 | "domain": domain, 102 | "scDate": scheduledDate.Format(time.RFC3339), 103 | }) 104 | 105 | _, err := s.client.Do(req) 106 | 107 | return err 108 | } 109 | 110 | // Info gets information about a domain. 111 | func (s *DomainService) Info(domain string, roID int) (*DomainInfoResponse, error) { 112 | req := s.client.NewRequest(methodDomainInfo, map[string]interface{}{ 113 | "domain": domain, 114 | "wide": "2", 115 | }) 116 | if roID != 0 { 117 | req.Args["roId"] = roID 118 | } 119 | 120 | resp, err := s.client.Do(req) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | var result DomainInfoResponse 126 | 127 | err = mapstructure.Decode(resp, &result) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return &result, nil 133 | } 134 | 135 | // List lists domains. 136 | func (s *DomainService) List(request *DomainListRequest) (*DomainList, error) { 137 | if request == nil { 138 | return nil, errors.New("request can't be nil") 139 | } 140 | 141 | requestMap := structs.Map(request) 142 | requestMap["wide"] = "2" 143 | 144 | req := s.client.NewRequest(methodDomainList, requestMap) 145 | 146 | resp, err := s.client.Do(req) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | // This is a (temporary) workaround to convert the API response from string to int. 152 | // The code only applies when string is being return, otherwise it's being skipped. 153 | // As per docs at https://www.inwx.com/en/help/apidoc/f/ch02s08.html#domain.list we should get int here, but apparently it's not the case. 154 | // Ticket 10026265 with INWX was raised. 155 | if countStr, ok := resp["count"].(string); ok { 156 | // If we land here, we got string back, but we expect int. 157 | // Converting value to int and writing it to the response. 158 | if num, ok := strconv.Atoi(countStr); ok == nil { 159 | resp["count"] = num 160 | } 161 | } 162 | 163 | var result DomainList 164 | 165 | err = mapstructure.Decode(resp, &result) 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | return &result, nil 171 | } 172 | 173 | // Whois information about a domains. 174 | func (s *DomainService) Whois(domain string) (string, error) { 175 | req := s.client.NewRequest(methodDomainWhois, map[string]interface{}{ 176 | "domain": domain, 177 | }) 178 | 179 | resp, err := s.client.Do(req) 180 | if err != nil { 181 | return "", err 182 | } 183 | 184 | var result map[string]string 185 | 186 | err = mapstructure.Decode(resp, &result) 187 | if err != nil { 188 | return "", err 189 | } 190 | 191 | return result["whois"], nil 192 | } 193 | 194 | // Update updates domain information. 195 | func (s *DomainService) Update(request *DomainUpdateRequest) (float32, error) { 196 | req := s.client.NewRequest(methodDomainUpdate, structs.Map(request)) 197 | 198 | resp, err := s.client.Do(req) 199 | if err != nil { 200 | return 0, err 201 | } 202 | 203 | var result DomainUpdateResponse 204 | 205 | err = mapstructure.Decode(resp, &result) 206 | if err != nil { 207 | return 0, err 208 | } 209 | 210 | return result.Price, nil 211 | } 212 | 213 | type domainCheckResponseRoot struct { 214 | Domains []DomainCheckResponse `mapstructure:"domain"` 215 | } 216 | 217 | // DomainCheckResponse API model. 218 | type DomainCheckResponse struct { 219 | Available int `mapstructure:"avail"` 220 | Status string `mapstructure:"status"` 221 | Name string `mapstructure:"name"` 222 | Domain string `mapstructure:"domain"` 223 | TLD string `mapstructure:"tld"` 224 | CheckMethod string `mapstructure:"checkmethod"` 225 | Price float32 `mapstructure:"price"` 226 | CheckTime float32 `mapstructure:"checktime"` 227 | } 228 | 229 | type domainPriceResponseRoot struct { 230 | Prices []DomainPriceResponse `mapstructure:"price"` 231 | } 232 | 233 | // DomainPriceResponse API model. 234 | type DomainPriceResponse struct { 235 | Tld string `mapstructure:"tld"` 236 | Currency string `mapstructure:"currency"` 237 | CreatePrice float32 `mapstructure:"createPrice"` 238 | MonthlyCreatePrice float32 `mapstructure:"monthlyCreatePrice"` 239 | TransferPrice float32 `mapstructure:"transferPrice"` 240 | RenewalPrice float32 `mapstructure:"renewalPrice"` 241 | MonthlyRenewalPrice float32 `mapstructure:"monthlyRenewalPrice"` 242 | UpdatePrice float32 `mapstructure:"updatePrice"` 243 | TradePrice float32 `mapstructure:"tradePrice"` 244 | TrusteePrice float32 `mapstructure:"trusteePrice"` 245 | MonthlyTrusteePrice float32 `mapstructure:"monthlyTrusteePrice"` 246 | CreatePeriod int `mapstructure:"createPeriod"` 247 | TransferPeriod int `mapstructure:"transferPeriod"` 248 | RenewalPeriod int `mapstructure:"renewalPeriod"` 249 | TradePeriod int `mapstructure:"tradePeriod"` 250 | } 251 | 252 | // DomainRegisterRequest API model. 253 | type DomainRegisterRequest struct { 254 | Domain string `structs:"domain"` 255 | Period string `structs:"period,omitempty"` 256 | Registrant int `structs:"registrant"` 257 | Admin int `structs:"admin"` 258 | Tech int `structs:"tech"` 259 | Billing int `structs:"billing"` 260 | Nameservers []string `structs:"ns,omitempty"` 261 | TransferLock string `structs:"transferLock,omitempty"` 262 | RenewalMode string `structs:"renewalMode,omitempty"` 263 | WhoisProvider string `structs:"whoisProvider,omitempty"` 264 | WhoisURL string `structs:"whoisUrl,omitempty"` 265 | ScDate string `structs:"scDate,omitempty"` 266 | ExtDate string `structs:"extDate,omitempty"` 267 | Asynchron string `structs:"asynchron,omitempty"` 268 | Voucher string `structs:"voucher,omitempty"` 269 | Testing string `structs:"testing,omitempty"` 270 | } 271 | 272 | // DomainRegisterResponse API model. 273 | type DomainRegisterResponse struct { 274 | RoID int `mapstructure:"roId"` 275 | Price float32 `mapstructure:"price"` 276 | Currency string `mapstructure:"currency"` 277 | } 278 | 279 | // DomainInfoResponse API model. 280 | type DomainInfoResponse struct { 281 | RoID int `mapstructure:"roId"` 282 | Domain string `mapstructure:"domain"` 283 | DomainAce string `mapstructure:"domainAce"` 284 | Period string `mapstructure:"period"` 285 | CrDate time.Time `mapstructure:"crDate"` 286 | ExDate time.Time `mapstructure:"exDate"` 287 | UpDate time.Time `mapstructure:"upDate"` 288 | ReDate time.Time `mapstructure:"reDate"` 289 | ScDate time.Time `mapstructure:"scDate"` 290 | TransferLock bool `mapstructure:"transferLock"` 291 | Status string `mapstructure:"status"` 292 | AuthCode string `mapstructure:"authCode"` 293 | RenewalMode string `mapstructure:"renewalMode"` 294 | TransferMode string `mapstructure:"transferMode"` 295 | Registrant int `mapstructure:"registrant"` 296 | Admin int `mapstructure:"admin"` 297 | Tech int `mapstructure:"tech"` 298 | Billing int `mapstructure:"billing"` 299 | Nameservers []string `mapstructure:"ns"` 300 | NoDelegation bool `mapstructure:"noDelegation"` 301 | Contacts map[string]Contact `mapstructure:"contact"` 302 | } 303 | 304 | // Contact API model. 305 | type Contact struct { 306 | RoID int `mapstructure:"roId"` 307 | ID string `mapstructure:"id"` 308 | Type string `mapstructure:"type"` 309 | Name string `mapstructure:"name"` 310 | Org string `mapstructure:"org"` 311 | Street string `mapstructure:"street"` 312 | City string `mapstructure:"city"` 313 | PostalCode string `mapstructure:"pc"` 314 | StateProvince string `mapstructure:"sp"` 315 | Country string `mapstructure:"cc"` 316 | Phone string `mapstructure:"voice"` 317 | Fax string `mapstructure:"fax"` 318 | Email string `mapstructure:"email"` 319 | Remarks string `mapstructure:"remarks"` 320 | Protection string `mapstructure:"protection"` 321 | } 322 | 323 | // DomainListRequest API model. 324 | type DomainListRequest struct { 325 | Domain string `structs:"domain,omitempty"` 326 | RoID int `structs:"roId,omitempty"` 327 | Status int `structs:"status,omitempty"` 328 | Registrant int `structs:"registrant,omitempty"` 329 | Admin int `structs:"admin,omitempty"` 330 | Tech int `structs:"tech,omitempty"` 331 | Billing int `structs:"billing,omitempty"` 332 | RenewalMode int `structs:"renewalMode,omitempty"` 333 | TransferLock int `structs:"transferLock,omitempty"` 334 | NoDelegation int `structs:"noDelegation,omitempty"` 335 | Tag int `structs:"tag,omitempty"` 336 | Order int `structs:"order,omitempty"` 337 | Page int `structs:"page,omitempty"` 338 | PageLimit int `structs:"pagelimit,omitempty"` 339 | } 340 | 341 | // DomainList API model. 342 | type DomainList struct { 343 | Count int `mapstructure:"count"` 344 | Domains []DomainInfoResponse `mapstructure:"domain"` 345 | } 346 | 347 | // DomainUpdateRequest API model. 348 | type DomainUpdateRequest struct { 349 | Domain string `structs:"domain"` 350 | Nameservers []string `structs:"ns,omitempty"` 351 | TransferLock int `structs:"transferLock,omitempty"` 352 | RenewalMode string `structs:"renewalMode,omitempty"` 353 | TransferMode string `structs:"transferMode,omitempty"` 354 | // unsupported fields: 355 | // registrant: New owner contact handle id (int, false) 356 | // admin: New administrative contact handle id (int, false) 357 | // tech: New technical contact handle id (int, false) 358 | // billing: New billing contact handle id (int, false) 359 | // authCode: Authorization code (if supported) (text64, false) 360 | // scDate: Time of scheduled execution (timestamp, false) 361 | // whoisProvider: Whois provider (token0255, false) 362 | // whoisUrl: Whois url (token0255, false) 363 | // extData: Domain extra data (extData, false) 364 | // asynchron: Asynchron domain update boolean (false, false) 365 | // testing: Execute command in testing mode boolean (false, false) 366 | } 367 | 368 | // DomainUpdateResponse API model. 369 | type DomainUpdateResponse struct { 370 | Price float32 `mapstructure:"price"` 371 | } 372 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nrdcg/goinwx 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/fatih/structs v1.1.0 7 | github.com/go-viper/mapstructure/v2 v2.2.1 8 | github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 2 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 3 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 4 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 5 | github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= 6 | github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= 7 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 8 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 9 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 10 | -------------------------------------------------------------------------------- /goinwx.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/kolo/xmlrpc" 7 | ) 8 | 9 | // API information. 10 | const ( 11 | APIBaseURL = "https://api.domrobot.com/xmlrpc/" 12 | APISandboxBaseURL = "https://api.ote.domrobot.com/xmlrpc/" 13 | APILanguage = "en" 14 | ) 15 | 16 | // Client manages communication with INWX API. 17 | type Client struct { 18 | // HTTP client used to communicate with the INWX API. 19 | RPCClient *xmlrpc.Client 20 | 21 | // API username and password 22 | username string 23 | password string 24 | 25 | lang string 26 | 27 | common service // Reuse a single struct instead of allocating one for each service on the heap. 28 | 29 | // Services used for communicating with the API 30 | Account *AccountService 31 | Contacts *ContactService 32 | Dnssec *DNSSecService 33 | Domains *DomainService 34 | Nameservers *NameserverService 35 | } 36 | 37 | type service struct { 38 | client *Client 39 | } 40 | 41 | // ClientOptions Options of the API client. 42 | type ClientOptions struct { 43 | Sandbox bool 44 | 45 | // Language of the return message. (en/de/es) 46 | Lang string 47 | 48 | // Base URL for API requests (only for client testing purpose). 49 | BaseURL *url.URL 50 | } 51 | 52 | // Request The representation of an API request. 53 | type Request struct { 54 | ServiceMethod string 55 | Args map[string]interface{} 56 | } 57 | 58 | // NewClient returns a new INWX API client. 59 | func NewClient(username, password string, opts *ClientOptions) *Client { 60 | baseURL := getBaseURL(opts).String() 61 | 62 | rpcClient, _ := xmlrpc.NewClient(baseURL, nil) 63 | 64 | client := &Client{ 65 | RPCClient: rpcClient, 66 | username: username, 67 | password: password, 68 | lang: APILanguage, 69 | } 70 | 71 | if opts != nil && opts.Lang != "" { 72 | client.lang = opts.Lang 73 | } 74 | 75 | client.common.client = client 76 | client.Account = (*AccountService)(&client.common) 77 | client.Contacts = (*ContactService)(&client.common) 78 | client.Dnssec = (*DNSSecService)(&client.common) 79 | client.Domains = (*DomainService)(&client.common) 80 | client.Nameservers = (*NameserverService)(&client.common) 81 | 82 | return client 83 | } 84 | 85 | // NewRequest creates an API request. 86 | func (c *Client) NewRequest(serviceMethod string, args map[string]interface{}) *Request { 87 | if args != nil { 88 | args["lang"] = APILanguage 89 | } 90 | 91 | return &Request{ServiceMethod: serviceMethod, Args: args} 92 | } 93 | 94 | // Do sends an API request and returns the API response. 95 | func (c *Client) Do(req *Request) (map[string]interface{}, error) { 96 | var resp Response 97 | 98 | err := c.RPCClient.Call(req.ServiceMethod, req.Args, &resp) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return resp.ResponseData, checkResponse(&resp) 104 | } 105 | 106 | // checkResponse checks the API response for errors, and returns them if present. 107 | func checkResponse(r *Response) error { 108 | if c := r.Code; c >= 1000 && c <= 1500 { 109 | return nil 110 | } 111 | 112 | return &ErrorResponse{Code: r.Code, Message: r.Message, Reason: r.Reason, ReasonCode: r.ReasonCode} 113 | } 114 | 115 | func getBaseURL(opts *ClientOptions) *url.URL { 116 | var useSandbox bool 117 | if opts != nil { 118 | useSandbox = opts.Sandbox 119 | } 120 | 121 | var baseURL *url.URL 122 | 123 | if useSandbox { 124 | baseURL, _ = url.Parse(APISandboxBaseURL) 125 | } else { 126 | baseURL, _ = url.Parse(APIBaseURL) 127 | } 128 | 129 | if opts != nil && opts.BaseURL != nil { 130 | baseURL = opts.BaseURL 131 | } 132 | 133 | return baseURL 134 | } 135 | -------------------------------------------------------------------------------- /nameserver.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/fatih/structs" 9 | "github.com/go-viper/mapstructure/v2" 10 | ) 11 | 12 | const ( 13 | methodNameserverCheck = "nameserver.check" 14 | methodNameserverCreate = "nameserver.create" 15 | methodNameserverCreateRecord = "nameserver.createRecord" 16 | methodNameserverDelete = "nameserver.delete" 17 | methodNameserverDeleteRecord = "nameserver.deleteRecord" 18 | methodNameserverInfo = "nameserver.info" 19 | methodNameserverList = "nameserver.list" 20 | methodNameserverUpdate = "nameserver.update" 21 | methodNameserverUpdateRecord = "nameserver.updateRecord" 22 | ) 23 | 24 | // NameserverService API access to Nameservers. 25 | type NameserverService service 26 | 27 | // Check checks a domain on nameservers. 28 | func (s *NameserverService) Check(domain string, nameservers []string) (*NameserverCheckResponse, error) { 29 | req := s.client.NewRequest(methodNameserverCheck, map[string]interface{}{ 30 | "domain": domain, 31 | "ns": nameservers, 32 | }) 33 | 34 | resp, err := s.client.Do(req) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | var result NameserverCheckResponse 40 | 41 | err = mapstructure.Decode(resp, &result) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return &result, nil 47 | } 48 | 49 | // Info gets information. 50 | func (s *NameserverService) Info(request *NameserverInfoRequest) (*NameserverInfoResponse, error) { 51 | req := s.client.NewRequest(methodNameserverInfo, structs.Map(request)) 52 | 53 | resp, err := s.client.Do(req) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | result := NameserverInfoResponse{} 59 | 60 | err = mapstructure.Decode(resp, &result) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return &result, nil 66 | } 67 | 68 | // List lists nameservers for a domain. 69 | // Deprecated: use ListWithParams instead. 70 | func (s *NameserverService) List(domain string) (*NameserverListResponse, error) { 71 | requestMap := map[string]interface{}{ 72 | "domain": "*", 73 | "wide": 2, 74 | } 75 | 76 | if domain != "" { 77 | requestMap["domain"] = domain 78 | } 79 | 80 | req := s.client.NewRequest(methodNameserverList, requestMap) 81 | 82 | resp, err := s.client.Do(req) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | result := NameserverListResponse{} 88 | 89 | err = mapstructure.Decode(resp, &result) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | return &result, nil 95 | } 96 | 97 | // ListWithParams lists nameservers for a domain. 98 | func (s *NameserverService) ListWithParams(request *NameserverListRequest) (*NameserverListResponse, error) { 99 | if request == nil { 100 | return nil, errors.New("request can't be nil") 101 | } 102 | 103 | requestMap := structs.Map(request) 104 | requestMap["wide"] = "2" 105 | 106 | req := s.client.NewRequest(methodNameserverList, requestMap) 107 | 108 | resp, err := s.client.Do(req) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | result := NameserverListResponse{} 114 | 115 | err = mapstructure.Decode(resp, &result) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return &result, nil 121 | } 122 | 123 | // Create creates a nameserver. 124 | func (s *NameserverService) Create(request *NameserverCreateRequest) (int, error) { 125 | req := s.client.NewRequest(methodNameserverCreate, structs.Map(request)) 126 | 127 | resp, err := s.client.Do(req) 128 | if err != nil { 129 | return 0, err 130 | } 131 | 132 | var result map[string]int 133 | 134 | err = mapstructure.Decode(resp, &result) 135 | if err != nil { 136 | return 0, err 137 | } 138 | 139 | return result["roId"], nil 140 | } 141 | 142 | // CreateRecord creates a DNS record. 143 | func (s *NameserverService) CreateRecord(request *NameserverRecordRequest) (int, error) { 144 | req := s.client.NewRequest(methodNameserverCreateRecord, structs.Map(request)) 145 | 146 | resp, err := s.client.Do(req) 147 | if err != nil { 148 | return 0, err 149 | } 150 | 151 | var result map[string]int 152 | 153 | err = mapstructure.Decode(resp, &result) 154 | if err != nil { 155 | return 0, err 156 | } 157 | 158 | return result["id"], nil 159 | } 160 | 161 | // UpdateRecord updates a DNS record. 162 | func (s *NameserverService) UpdateRecord(recID int, request *NameserverRecordRequest) error { 163 | if request == nil { 164 | return errors.New("request can't be nil") 165 | } 166 | 167 | requestMap := structs.Map(request) 168 | requestMap["id"] = recID 169 | 170 | req := s.client.NewRequest(methodNameserverUpdateRecord, requestMap) 171 | 172 | _, err := s.client.Do(req) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | return nil 178 | } 179 | 180 | // DeleteRecord deletes a DNS record. 181 | func (s *NameserverService) DeleteRecord(recID int) error { 182 | req := s.client.NewRequest(methodNameserverDeleteRecord, map[string]interface{}{ 183 | "id": recID, 184 | }) 185 | 186 | _, err := s.client.Do(req) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | return nil 192 | } 193 | 194 | // FindRecordByID search a DNS record by ID. 195 | func (s *NameserverService) FindRecordByID(recID int) (*NameserverRecord, *NameserverDomain, error) { 196 | listResp, err := s.client.Nameservers.ListWithParams(&NameserverListRequest{}) 197 | if err != nil { 198 | return nil, nil, err 199 | } 200 | 201 | for _, domainItem := range listResp.Domains { 202 | resp, err := s.client.Nameservers.Info(&NameserverInfoRequest{RoID: domainItem.RoID}) 203 | if err != nil { 204 | return nil, nil, err 205 | } 206 | 207 | for _, record := range resp.Records { 208 | if record.ID == recID { 209 | return &record, &domainItem, nil 210 | } 211 | } 212 | } 213 | 214 | return nil, nil, fmt.Errorf("couldn't find INWX Record for id %d", recID) 215 | } 216 | 217 | // NameserverCheckResponse API model. 218 | type NameserverCheckResponse struct { 219 | Details []string `mapstructure:"details"` 220 | Status string `mapstructure:"status"` 221 | } 222 | 223 | // NameserverRecordRequest API model. 224 | type NameserverRecordRequest struct { 225 | RoID int `structs:"roId,omitempty"` 226 | Domain string `structs:"domain,omitempty"` 227 | Type string `structs:"type"` 228 | Content string `structs:"content"` 229 | Name string `structs:"name,omitempty"` 230 | TTL int `structs:"ttl,omitempty"` 231 | Priority int `structs:"prio,omitempty"` 232 | URLAppend bool `structs:"urlAppend,omitempty"` 233 | URLRedirectType string `structs:"urlRedirectType,omitempty"` 234 | URLRedirectTitle string `structs:"urlRedirectTitle,omitempty"` 235 | URLRedirectDescription string `structs:"urlRedirectDescription,omitempty"` 236 | URLRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` 237 | URLRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` 238 | } 239 | 240 | // NameserverCreateRequest API model. 241 | type NameserverCreateRequest struct { 242 | Domain string `structs:"domain"` 243 | Type string `structs:"type"` 244 | Nameservers []string `structs:"ns,omitempty"` 245 | MasterIP string `structs:"masterIp,omitempty"` 246 | Web string `structs:"web,omitempty"` 247 | Mail string `structs:"mail,omitempty"` 248 | SoaEmail string `structs:"soaEmail,omitempty"` 249 | URLRedirectType string `structs:"urlRedirectType,omitempty"` 250 | URLRedirectTitle string `structs:"urlRedirectTitle,omitempty"` 251 | URLRedirectDescription string `structs:"urlRedirectDescription,omitempty"` 252 | URLRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` 253 | URLRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` 254 | Testing bool `structs:"testing,omitempty"` 255 | } 256 | 257 | // NameserverInfoRequest API model. 258 | type NameserverInfoRequest struct { 259 | Domain string `structs:"domain,omitempty"` 260 | RoID int `structs:"roId,omitempty"` 261 | RecordID int `structs:"recordId,omitempty"` 262 | Type string `structs:"type,omitempty"` 263 | Name string `structs:"name,omitempty"` 264 | Content string `structs:"content,omitempty"` 265 | TTL int `structs:"ttl,omitempty"` 266 | Priority int `structs:"prio,omitempty"` 267 | } 268 | 269 | // NamserverInfoResponse API model. 270 | // Deprecated: Use NameserverInfoResponse instead. 271 | type NamserverInfoResponse = NameserverInfoResponse 272 | 273 | // NameserverInfoResponse API model. 274 | type NameserverInfoResponse struct { 275 | RoID int `mapstructure:"roId"` 276 | Domain string `mapstructure:"domain"` 277 | Type string `mapstructure:"type"` 278 | MasterIP string `mapstructure:"masterIp"` 279 | LastZoneCheck time.Time `mapstructure:"lastZoneCheck"` 280 | SlaveDNS []SlaveInfo `mapstructure:"slaveDns"` 281 | SOASerial string `mapstructure:"SOAserial"` 282 | Count int `mapstructure:"count"` 283 | Records []NameserverRecord `mapstructure:"record"` 284 | } 285 | 286 | // SlaveInfo API model. 287 | type SlaveInfo struct { 288 | Name string `mapstructure:"name"` 289 | IP string `mapstructure:"ip"` 290 | } 291 | 292 | // NameserverRecord API model. 293 | type NameserverRecord struct { 294 | ID int `mapstructure:"id"` 295 | Name string `mapstructure:"name"` 296 | Type string `mapstructure:"type"` 297 | Content string `mapstructure:"content"` 298 | TTL int `mapstructure:"TTL"` 299 | Priority int `mapstructure:"prio"` 300 | URLAppend bool `mapstructure:"urlAppend,omitempty"` 301 | URLRedirectType string `mapstructure:"urlRedirectType"` 302 | URLRedirectTitle string `mapstructure:"urlRedirectTitle"` 303 | URLRedirectDescription string `mapstructure:"urlRedirectDescription"` 304 | URLRedirectKeywords string `mapstructure:"urlRedirectKeywords"` 305 | URLRedirectFavIcon string `mapstructure:"urlRedirectFavIcon"` 306 | } 307 | 308 | // NameserverListRequest API model. 309 | type NameserverListRequest struct { 310 | Domain string `structs:"domain,omitempty"` 311 | Wide int `structs:"wide,omitempty"` 312 | Page int `structs:"page,omitempty"` 313 | PageLimit int `structs:"pagelimit,omitempty"` 314 | } 315 | 316 | // NamserverListResponse API model. 317 | // Deprecated: Use NameserverListResponse instead. 318 | type NamserverListResponse = NameserverListResponse 319 | 320 | // NameserverListResponse API model. 321 | type NameserverListResponse struct { 322 | Count int `mapstructure:"count"` 323 | Domains []NameserverDomain `mapstructure:"domains"` 324 | } 325 | 326 | // NameserverDomain API model. 327 | type NameserverDomain struct { 328 | RoID int `mapstructure:"roId"` 329 | Domain string `mapstructure:"domain"` 330 | Type string `mapstructure:"type"` 331 | MasterIP string `mapstructure:"masterIp"` 332 | Mail string `mapstructure:"mail"` 333 | Web string `mapstructure:"web"` 334 | URL string `mapstructure:"url"` 335 | Ipv4 string `mapstructure:"ipv4"` 336 | Ipv6 string `mapstructure:"ipv6"` 337 | } 338 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package goinwx 2 | 3 | import "fmt" 4 | 5 | // Response is a INWX API response. This wraps the standard http.Response returned from INWX. 6 | type Response struct { 7 | Code int `xmlrpc:"code"` 8 | Message string `xmlrpc:"msg"` 9 | ReasonCode string `xmlrpc:"reasonCode"` 10 | Reason string `xmlrpc:"reason"` 11 | ResponseData map[string]interface{} `xmlrpc:"resData"` 12 | } 13 | 14 | // An ErrorResponse reports the error caused by an API request. 15 | type ErrorResponse struct { 16 | Code int `xmlrpc:"code"` 17 | Message string `xmlrpc:"msg"` 18 | ReasonCode string `xmlrpc:"reasonCode"` 19 | Reason string `xmlrpc:"reason"` 20 | } 21 | 22 | func (r *ErrorResponse) Error() string { 23 | if r.Reason != "" { 24 | return fmt.Sprintf("(%d) %s. Reason: (%s) %s", 25 | r.Code, r.Message, r.ReasonCode, r.Reason) 26 | } 27 | 28 | return fmt.Sprintf("(%d) %s", r.Code, r.Message) 29 | } 30 | --------------------------------------------------------------------------------