pax_global_header00006660000000000000000000000064151120715010014503gustar00rootroot0000000000000052 comment=9eda2a53570e4fb79e3076c5e99ac5965828173d goinwx-0.12.0/000077500000000000000000000000001511207150100130765ustar00rootroot00000000000000goinwx-0.12.0/.github/000077500000000000000000000000001511207150100144365ustar00rootroot00000000000000goinwx-0.12.0/.github/workflows/000077500000000000000000000000001511207150100164735ustar00rootroot00000000000000goinwx-0.12.0/.github/workflows/go-cross.yml000066400000000000000000000011531511207150100207520ustar00rootroot00000000000000name: Go Matrix on: push: branches: - main - master pull_request: branches: - main - master jobs: cross: name: Go runs-on: ${{ matrix.os }} env: CGO_ENABLED: 0 strategy: matrix: go-version: [ stable, oldstable ] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Test run: go test -v -cover ./... - name: Build run: go build -v -ldflags "-s -w" -trimpath goinwx-0.12.0/.github/workflows/main.yml000066400000000000000000000014131511207150100201410ustar00rootroot00000000000000name: Main on: push: branches: - main - master pull_request: branches: - main - master jobs: main: name: Main Process runs-on: ubuntu-latest env: GO_VERSION: stable GOLANGCI_LINT_VERSION: v2.6 CGO_ENABLED: 0 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - name: Check and get dependencies run: | go mod tidy git diff --exit-code go.mod git diff --exit-code go.sum go mod download - uses: golangci/golangci-lint-action@v9 with: install-only: true version: ${{ env.GOLANGCI_LINT_VERSION }} - name: Make run: make goinwx-0.12.0/.gitignore000066400000000000000000000000371511207150100150660ustar00rootroot00000000000000*.iml .idea vendor dist builds goinwx-0.12.0/.golangci.yml000066400000000000000000000031531511207150100154640ustar00rootroot00000000000000version: "2" formatters: enable: - gci - gofumpt settings: gofumpt: extra-rules: true linters: default: all disable: - cyclop # duplicate of gocyclo - dupl - err113 - errname # Require a breaking change. - exhaustive - exhaustruct - forcetypeassert - lll - mnd - nlreturn - noctx - paralleltest - prealloc - rowserrcheck # not relevant (SQL) - sqlclosecheck # not relevant (SQL) - testpackage - tparallel - wrapcheck - noinlineerr - wsl # deprecated settings: depguard: rules: main: deny: - pkg: github.com/instana/testify desc: not allowed - pkg: github.com/pkg/errors desc: Should be replaced by standard lib errors package funlen: lines: -1 statements: 40 goconst: min-len: 5 min-occurrences: 3 gocritic: disabled-checks: - sloppyReassign - rangeValCopy - octalLiteral - paramTypeCombine # already handle by gofumpt.extra-rules enabled-tags: - diagnostic - style - performance settings: hugeParam: sizeThreshold: 100 gocyclo: min-complexity: 15 godox: keywords: - FIXME govet: disable: - fieldalignment enable-all: true misspell: locale: US exclusions: warn-unused: true presets: - comments rules: - linters: - bodyclose - funlen path: .*_test.go issues: max-issues-per-linter: 0 max-same-issues: 0 goinwx-0.12.0/LICENSE000066400000000000000000000021151511207150100141020ustar00rootroot00000000000000MIT License Copyright (c) 2017 Andrew Copyright (c) 2018-2024 NRDCG authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. goinwx-0.12.0/Makefile000066400000000000000000000002561511207150100145410ustar00rootroot00000000000000.PHONY: default clean check test default: clean check test build test: clean go test -v -cover ./... clean: rm -f cover.out build: go build check: golangci-lint run goinwx-0.12.0/README.md000066400000000000000000000056341511207150100143650ustar00rootroot00000000000000# INWX Go API client [![Build Status](https://github.com/nrdcg/goinwx/workflows/Main/badge.svg?branch=master)](https://github.com/nrdcg/goinwx/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/nrdcg/goinwx)](https://pkg.go.dev/github.com/nrdcg/goinwx) [![Go Report Card](https://goreportcard.com/badge/github.com/nrdcg/goinwx)](https://goreportcard.com/report/github.com/nrdcg/goinwx) This go library implements some parts of the official INWX XML-RPC API. ## API ```go package main import ( "log" "github.com/nrdcg/goinwx" ) func main() { client := goinwx.NewClient("username", "password", &goinwx.ClientOptions{Sandbox: true}) _, err := client.Account.Login() if err != nil { log.Fatal(err) } defer func() { if err := client.Account.Logout(); err != nil { log.Printf("inwx: failed to logout: %v", err) } }() var request = &goinwx.NameserverRecordRequest{ Domain: "domain.com", Name: "foo.domain.com.", Type: "TXT", Content: "aaa", TTL: 300, } _, err = client.Nameservers.CreateRecord(request) if err != nil { log.Fatal(err) } } ``` ### Using 2FA If it is desired to use 2FA without manual entering the TOTP every time, you must set the parameter `otp-key` to the secret that is shown during the setup of 2FA for you INWX account. Otherwise, you can skip `totp.GenerateCode` step and enter the verification code of the Google Authenticator app every time manually. The `otp-key` looks something like `EELTWFL55ESIHPTJAAHBCY7LXBZARUOJ`. ```go package main import ( "log" "time" "github.com/nrdcg/goinwx" "github.com/pquerna/otp/totp" ) func main() { client := goinwx.NewClient("username", "password", &goinwx.ClientOptions{Sandbox: true}) resp, err := client.Account.Login() if err != nil { log.Fatal(err) } if resp.TFA != "GOOGLE-AUTH" { log.Fatal("unsupported 2 Factor Authentication") } tan, err := totp.GenerateCode("otp-key", time.Now()) if err != nil { log.Fatal(err) } err = client.Account.Unlock(tan) if err != nil { log.Fatal(err) } defer func() { if err := client.Account.Logout(); err != nil { log.Printf("inwx: failed to logout: %v", err) } }() request := &goinwx.NameserverRecordRequest{ Domain: "domain.com", Name: "foo.domain.com.", Type: "TXT", Content: "aaa", TTL: 300, } _, err = client.Nameservers.CreateRecord(request) if err != nil { log.Fatal(err) } } ``` ## Supported Features Full API documentation can be found [here](https://www.inwx.de/en/help/apidoc). The following parts are implemented: * Account * Login * Logout * Lock * Unlock (with mobile TAN) * Domains * Check * Register * Delete * Info * GetPrices * List * Whois * Update * Nameservers * Check * Create * Info * List * CreateRecord * UpdateRecord * DeleteRecord * FindRecordById * Contacts * List * Info * Create * Update * Delete ## Contributions Your contributions are very appreciated. goinwx-0.12.0/account.go000066400000000000000000000027651511207150100150730ustar00rootroot00000000000000package goinwx import "github.com/go-viper/mapstructure/v2" const ( methodAccountLogin = "account.login" methodAccountLogout = "account.logout" methodAccountLock = "account.lock" methodAccountUnlock = "account.unlock" ) // AccountService API access to Account. type AccountService service // Login Account login. func (s *AccountService) Login() (*LoginResponse, error) { req := s.client.NewRequest(methodAccountLogin, map[string]any{ "user": s.client.username, "pass": s.client.password, }) resp, err := s.client.Do(req) if err != nil { return nil, err } var result LoginResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // Logout Account logout. func (s *AccountService) Logout() error { req := s.client.NewRequest(methodAccountLogout, nil) _, err := s.client.Do(req) return err } // Lock Account lock. func (s *AccountService) Lock() error { req := s.client.NewRequest(methodAccountLock, nil) _, err := s.client.Do(req) return err } // Unlock Account unlock. func (s *AccountService) Unlock(tan string) error { req := s.client.NewRequest(methodAccountUnlock, map[string]any{ "tan": tan, }) _, err := s.client.Do(req) return err } // LoginResponse API model. type LoginResponse struct { CustomerID int64 `mapstructure:"customerId"` AccountID int64 `mapstructure:"accountId"` TFA string `mapstructure:"tfa"` BuildDate string `mapstructure:"builddate"` Version string `mapstructure:"version"` } goinwx-0.12.0/contact.go000066400000000000000000000074271511207150100150720ustar00rootroot00000000000000package goinwx import ( "github.com/fatih/structs" "github.com/go-viper/mapstructure/v2" ) const ( methodContactInfo = "contact.info" methodContactList = "contact.list" methodContactCreate = "contact.create" methodContactDelete = "contact.delete" methodContactUpdate = "contact.update" ) // ContactService API access to Contact. type ContactService service // Create Creates a contact. func (s *ContactService) Create(request *ContactCreateRequest) (int, error) { req := s.client.NewRequest(methodContactCreate, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return 0, err } result := make(map[string]int) err = mapstructure.Decode(resp, &result) if err != nil { return 0, err } return result["id"], nil } // Delete Deletes a contact. func (s *ContactService) Delete(roID int) error { req := s.client.NewRequest(methodContactDelete, map[string]any{ "id": roID, }) _, err := s.client.Do(req) return err } // Update Updates a contact. func (s *ContactService) Update(request *ContactUpdateRequest) error { req := s.client.NewRequest(methodContactUpdate, structs.Map(request)) _, err := s.client.Do(req) return err } // Info Get information about a contact. func (s *ContactService) Info(contactID int) (*ContactInfoResponse, error) { requestMap := make(map[string]any) requestMap["wide"] = 1 if contactID != 0 { requestMap["id"] = contactID } req := s.client.NewRequest(methodContactInfo, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } result := ContactInfoResponse{} err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // List Search contacts. func (s *ContactService) List(search string) (*ContactListResponse, error) { requestMap := make(map[string]any) if search != "" { requestMap["search"] = search } req := s.client.NewRequest(methodContactList, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } result := ContactListResponse{} err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // ContactCreateRequest API model. type ContactCreateRequest struct { Type string `structs:"type"` Name string `structs:"name"` Org string `structs:"org,omitempty"` Street string `structs:"street"` City string `structs:"city"` PostalCode string `structs:"pc"` StateProvince string `structs:"sp,omitempty"` CountryCode string `structs:"cc"` Voice string `structs:"voice"` Fax string `structs:"fax,omitempty"` Email string `structs:"email"` Remarks string `structs:"remarks,omitempty"` Protection bool `structs:"protection,omitempty"` Testing bool `structs:"testing,omitempty"` } // ContactUpdateRequest API model. type ContactUpdateRequest struct { ID int `structs:"id"` Name string `structs:"name,omitempty"` Org string `structs:"org,omitempty"` Street string `structs:"street,omitempty"` City string `structs:"city,omitempty"` PostalCode string `structs:"pc,omitempty"` StateProvince string `structs:"sp,omitempty"` CountryCode string `structs:"cc,omitempty"` Voice string `structs:"voice,omitempty"` Fax string `structs:"fax,omitempty"` Email string `structs:"email,omitempty"` Remarks string `structs:"remarks,omitempty"` Protection bool `structs:"protection,omitempty"` Testing bool `structs:"testing,omitempty"` } // ContactInfoResponse API model. type ContactInfoResponse struct { Contact Contact `mapstructure:"contact"` } // ContactListResponse API model. type ContactListResponse struct { Count int `mapstructure:"count"` Contacts []Contact `mapstructure:"contact"` } goinwx-0.12.0/dnssec.go000066400000000000000000000126401511207150100147070ustar00rootroot00000000000000package goinwx import ( "errors" "time" "github.com/fatih/structs" "github.com/go-viper/mapstructure/v2" ) const ( methodDNSSecAddDNSKey = "dnssec.adddnskey" methodDNSSecDeleteAll = "dnssec.deleteall" methodDNSSecDeleteDNSKey = "dnssec.deletednskey" methodDNSSecDisableDNSSec = "dnssec.disablednssec" methodDNSSecEnableDNSSec = "dnssec.enablednssec" methodDNSSecInfo = "dnssec.info" methodDNSSecListKeys = "dnssec.listkeys" ) // DNSSecService API access to DNSSEC. type DNSSecService service // Add adds one DNSKEY to a specified domain. func (s *DNSSecService) Add(request *DNSSecAddRequest) (*DNSSecAddResponse, error) { if request == nil { return nil, errors.New("request can't be nil") } requestMap := structs.Map(request) req := s.client.NewRequest(methodDNSSecAddDNSKey, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } var result DNSSecAddResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // DeleteAll deletes all DNSKEY/DS entries for a domain. func (s *DNSSecService) DeleteAll(domain string) error { req := s.client.NewRequest(methodDNSSecDeleteAll, map[string]any{ "domainName": domain, }) _, err := s.client.Do(req) if err != nil { return err } return nil } // DeleteDNSKey deletes one DNSKEY from a specified domain. func (s *DNSSecService) DeleteDNSKey(key string) error { req := s.client.NewRequest(methodDNSSecDeleteDNSKey, map[string]any{ "key": key, }) _, err := s.client.Do(req) if err != nil { return err } return nil } // Disable disables automated DNSSEC management for a domain. func (s *DNSSecService) Disable(domain string) error { req := s.client.NewRequest(methodDNSSecDisableDNSSec, map[string]any{ "domainName": domain, }) _, err := s.client.Do(req) if err != nil { return err } return nil } // Enable enables automated DNSSEC management for a domain. func (s *DNSSecService) Enable(domain string) error { req := s.client.NewRequest(methodDNSSecEnableDNSSec, map[string]any{ "domainName": domain, }) _, err := s.client.Do(req) if err != nil { return err } return nil } // Info gets current DNSSEC information. func (s *DNSSecService) Info(domains []string) (*DNSSecInfoResponse, error) { req := s.client.NewRequest(methodDNSSecInfo, map[string]any{ "domains": domains, }) resp, err := s.client.Do(req) if err != nil { return nil, err } var result DNSSecInfoResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // List lists domains. func (s *DNSSecService) List(request *DNSSecServiceListRequest) (*DNSSecServiceList, error) { if request == nil { return nil, errors.New("request can't be nil") } requestMap := structs.Map(request) req := s.client.NewRequest(methodDNSSecListKeys, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } var result DNSSecServiceList err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // DNSSecAddRequest API model. type DNSSecAddRequest struct { DomainName string `structs:"domainName,omitempty"` DNSKey string `structs:"dnskey,omitempty"` DS string `structs:"ds,omitempty"` CalculateDigest bool `structs:"calculateDigest,omitempty"` DigestType int `structs:"digestType,omitempty"` } // DNSSecAddResponse API model. type DNSSecAddResponse struct { DNSKey string `mapstructure:"dnskey"` DS string `mapstructure:"ds"` } // DNSSecInfoResponse API model. type DNSSecInfoResponse struct { Data []DNSSecInfo `mapstructure:"data"` } // DNSSecInfo API model. type DNSSecInfo struct { Domain string `mapstructure:"domain"` KeyCount int `mapstructure:"keyCount"` DNSSecStatus string `mapstructure:"dnsSecStatus"` } // DNSSecServiceListRequest API model. type DNSSecServiceListRequest struct { DomainName string `structs:"domainName,omitempty"` DomainNameIdn string `structs:"domainNameIdn,omitempty"` KeyTag int `structs:"keyTag,omitempty"` FlagID int `structs:"flagId,omitempty"` AlgorithmID int `structs:"algorithmId,omitempty"` PublicKey string `structs:"publicKey,omitempty"` DigestTypeID int `structs:"digestTypeId,omitempty"` Digest string `structs:"digest,omitempty"` CreatedBefore string `structs:"createdBefore,omitempty"` CreatedAfter string `structs:"createdAfter,omitempty"` Status string `structs:"status,omitempty"` Active int `structs:"active,omitempty"` Page int `structs:"page,omitempty"` PageLimit int `structs:"pagelimit,omitempty"` } // DNSSecServiceList API model. type DNSSecServiceList struct { DNSKeys []DNSSecServiceListResponse `mapstructure:"dnskey"` } // DNSSecServiceListResponse API model. type DNSSecServiceListResponse struct { OwnerName string `mapstructure:"ownerName"` ID int `mapstructure:"id"` DomainID int `mapstructure:"domainId"` KeyTag int `mapstructure:"keyTag"` FlagID int `mapstructure:"flagId"` AlgorithmID int `mapstructure:"algorithmId"` PublicKey string `mapstructure:"publicKey"` DigestTypeID int `mapstructure:"digestTypeId"` Digest string `mapstructure:"digest"` Created time.Time `mapstructure:"created"` Status string `mapstructure:"status"` Active int `mapstructure:"active"` } goinwx-0.12.0/domain.go000066400000000000000000000265211511207150100147020ustar00rootroot00000000000000package goinwx import ( "errors" "strconv" "time" "github.com/fatih/structs" "github.com/go-viper/mapstructure/v2" ) const ( methodDomainCheck = "domain.check" methodDomainCreate = "domain.create" methodDomainDelete = "domain.delete" methodDomainGetPrices = "domain.getPrices" methodDomainGetRules = "domain.getRules" methodDomainInfo = "domain.info" methodDomainList = "domain.list" methodDomainLog = "domain.log" methodDomainPush = "domain.push" methodDomainRenew = "domain.renew" methodDomainRestore = "domain.restore" methodDomainStats = "domain.stats" methodDomainTrade = "domain.trade" methodDomainTransfer = "domain.transfer" methodDomainTransferOut = "domain.transferOut" methodDomainUpdate = "domain.update" methodDomainWhois = "domain.whois" ) // DomainService API access to Domain. type DomainService service // Check checks domains. func (s *DomainService) Check(domains []string) ([]DomainCheckResponse, error) { req := s.client.NewRequest(methodDomainCheck, map[string]any{ "domain": domains, "wide": "2", }) resp, err := s.client.Do(req) if err != nil { return nil, err } root := new(domainCheckResponseRoot) err = mapstructure.Decode(resp, &root) if err != nil { return nil, err } return root.Domains, nil } // GetPrices gets TLDS prices. func (s *DomainService) GetPrices(tlds []string) ([]DomainPriceResponse, error) { req := s.client.NewRequest(methodDomainGetPrices, map[string]any{ "tld": tlds, "vat": false, }) resp, err := s.client.Do(req) if err != nil { return nil, err } root := new(domainPriceResponseRoot) err = mapstructure.Decode(resp, &root) if err != nil { return nil, err } return root.Prices, nil } // Register registers a domain. func (s *DomainService) Register(request *DomainRegisterRequest) (*DomainRegisterResponse, error) { req := s.client.NewRequest(methodDomainCreate, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return nil, err } var result DomainRegisterResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // Delete deletes a domain. func (s *DomainService) Delete(domain string, scheduledDate time.Time) error { req := s.client.NewRequest(methodDomainDelete, map[string]any{ "domain": domain, "scDate": scheduledDate.Format(time.RFC3339), }) _, err := s.client.Do(req) return err } // Info gets information about a domain. func (s *DomainService) Info(domain string, roID int) (*DomainInfoResponse, error) { req := s.client.NewRequest(methodDomainInfo, map[string]any{ "domain": domain, "wide": "2", }) if roID != 0 { req.Args["roId"] = roID } resp, err := s.client.Do(req) if err != nil { return nil, err } var result DomainInfoResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // List lists domains. func (s *DomainService) List(request *DomainListRequest) (*DomainList, error) { if request == nil { return nil, errors.New("request can't be nil") } requestMap := structs.Map(request) requestMap["wide"] = "2" req := s.client.NewRequest(methodDomainList, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } // This is a (temporary) workaround to convert the API response from string to int. // The code only applies when string is being return, otherwise it's being skipped. // 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. // Ticket 10026265 with INWX was raised. if countStr, ok := resp["count"].(string); ok { // If we land here, we got string back, but we expect int. // Converting value to int and writing it to the response. if num, ok := strconv.Atoi(countStr); ok == nil { resp["count"] = num } } var result DomainList err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // Whois information about a domains. func (s *DomainService) Whois(domain string) (string, error) { req := s.client.NewRequest(methodDomainWhois, map[string]any{ "domain": domain, }) resp, err := s.client.Do(req) if err != nil { return "", err } var result map[string]string err = mapstructure.Decode(resp, &result) if err != nil { return "", err } return result["whois"], nil } // Update updates domain information. func (s *DomainService) Update(request *DomainUpdateRequest) (float32, error) { req := s.client.NewRequest(methodDomainUpdate, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return 0, err } var result DomainUpdateResponse err = mapstructure.Decode(resp, &result) if err != nil { return 0, err } return result.Price, nil } type domainCheckResponseRoot struct { Domains []DomainCheckResponse `mapstructure:"domain"` } // DomainCheckResponse API model. type DomainCheckResponse struct { Available int `mapstructure:"avail"` Status string `mapstructure:"status"` Name string `mapstructure:"name"` Domain string `mapstructure:"domain"` TLD string `mapstructure:"tld"` CheckMethod string `mapstructure:"checkmethod"` Price float32 `mapstructure:"price"` CheckTime float32 `mapstructure:"checktime"` } type domainPriceResponseRoot struct { Prices []DomainPriceResponse `mapstructure:"price"` } // DomainPriceResponse API model. type DomainPriceResponse struct { Tld string `mapstructure:"tld"` Currency string `mapstructure:"currency"` CreatePrice float32 `mapstructure:"createPrice"` MonthlyCreatePrice float32 `mapstructure:"monthlyCreatePrice"` TransferPrice float32 `mapstructure:"transferPrice"` RenewalPrice float32 `mapstructure:"renewalPrice"` MonthlyRenewalPrice float32 `mapstructure:"monthlyRenewalPrice"` UpdatePrice float32 `mapstructure:"updatePrice"` TradePrice float32 `mapstructure:"tradePrice"` TrusteePrice float32 `mapstructure:"trusteePrice"` MonthlyTrusteePrice float32 `mapstructure:"monthlyTrusteePrice"` CreatePeriod int `mapstructure:"createPeriod"` TransferPeriod int `mapstructure:"transferPeriod"` RenewalPeriod int `mapstructure:"renewalPeriod"` TradePeriod int `mapstructure:"tradePeriod"` } // DomainRegisterRequest API model. type DomainRegisterRequest struct { Domain string `structs:"domain"` Period string `structs:"period,omitempty"` Registrant int `structs:"registrant"` Admin int `structs:"admin"` Tech int `structs:"tech"` Billing int `structs:"billing"` Nameservers []string `structs:"ns,omitempty"` TransferLock string `structs:"transferLock,omitempty"` RenewalMode string `structs:"renewalMode,omitempty"` WhoisProvider string `structs:"whoisProvider,omitempty"` WhoisURL string `structs:"whoisUrl,omitempty"` ScDate string `structs:"scDate,omitempty"` ExtDate string `structs:"extDate,omitempty"` Asynchron string `structs:"asynchron,omitempty"` Voucher string `structs:"voucher,omitempty"` Testing string `structs:"testing,omitempty"` } // DomainRegisterResponse API model. type DomainRegisterResponse struct { RoID int `mapstructure:"roId"` Price float32 `mapstructure:"price"` Currency string `mapstructure:"currency"` } // DomainInfoResponse API model. type DomainInfoResponse struct { RoID int `mapstructure:"roId"` Domain string `mapstructure:"domain"` DomainAce string `mapstructure:"domainAce"` Period string `mapstructure:"period"` CrDate time.Time `mapstructure:"crDate"` ExDate time.Time `mapstructure:"exDate"` UpDate time.Time `mapstructure:"upDate"` ReDate time.Time `mapstructure:"reDate"` ScDate time.Time `mapstructure:"scDate"` TransferLock bool `mapstructure:"transferLock"` Status string `mapstructure:"status"` AuthCode string `mapstructure:"authCode"` RenewalMode string `mapstructure:"renewalMode"` TransferMode string `mapstructure:"transferMode"` Registrant int `mapstructure:"registrant"` Admin int `mapstructure:"admin"` Tech int `mapstructure:"tech"` Billing int `mapstructure:"billing"` Nameservers []string `mapstructure:"ns"` NoDelegation bool `mapstructure:"noDelegation"` Contacts map[string]Contact `mapstructure:"contact"` } // Contact API model. type Contact struct { RoID int `mapstructure:"roId"` ID string `mapstructure:"id"` Type string `mapstructure:"type"` Name string `mapstructure:"name"` Org string `mapstructure:"org"` Street string `mapstructure:"street"` City string `mapstructure:"city"` PostalCode string `mapstructure:"pc"` StateProvince string `mapstructure:"sp"` Country string `mapstructure:"cc"` Phone string `mapstructure:"voice"` Fax string `mapstructure:"fax"` Email string `mapstructure:"email"` Remarks string `mapstructure:"remarks"` Protection string `mapstructure:"protection"` } // DomainListRequest API model. type DomainListRequest struct { Domain string `structs:"domain,omitempty"` RoID int `structs:"roId,omitempty"` Status int `structs:"status,omitempty"` Registrant int `structs:"registrant,omitempty"` Admin int `structs:"admin,omitempty"` Tech int `structs:"tech,omitempty"` Billing int `structs:"billing,omitempty"` RenewalMode int `structs:"renewalMode,omitempty"` TransferLock int `structs:"transferLock,omitempty"` NoDelegation int `structs:"noDelegation,omitempty"` Tag int `structs:"tag,omitempty"` Order int `structs:"order,omitempty"` Page int `structs:"page,omitempty"` PageLimit int `structs:"pagelimit,omitempty"` } // DomainList API model. type DomainList struct { Count int `mapstructure:"count"` Domains []DomainInfoResponse `mapstructure:"domain"` } // DomainUpdateRequest API model. type DomainUpdateRequest struct { Domain string `structs:"domain"` Nameservers []string `structs:"ns,omitempty"` TransferLock int `structs:"transferLock,omitempty"` RenewalMode string `structs:"renewalMode,omitempty"` TransferMode string `structs:"transferMode,omitempty"` // unsupported fields: // registrant: New owner contact handle id (int, false) // admin: New administrative contact handle id (int, false) // tech: New technical contact handle id (int, false) // billing: New billing contact handle id (int, false) // authCode: Authorization code (if supported) (text64, false) // scDate: Time of scheduled execution (timestamp, false) // whoisProvider: Whois provider (token0255, false) // whoisUrl: Whois url (token0255, false) // extData: Domain extra data (extData, false) // asynchron: Asynchron domain update boolean (false, false) // testing: Execute command in testing mode boolean (false, false) } // DomainUpdateResponse API model. type DomainUpdateResponse struct { Price float32 `mapstructure:"price"` } goinwx-0.12.0/go.mod000066400000000000000000000002751511207150100142100ustar00rootroot00000000000000module github.com/nrdcg/goinwx go 1.22 require ( github.com/fatih/structs v1.1.0 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b ) goinwx-0.12.0/go.sum000066400000000000000000000015051511207150100142320ustar00rootroot00000000000000github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= goinwx-0.12.0/goinwx.go000066400000000000000000000057141511207150100147470ustar00rootroot00000000000000package goinwx import ( "net/url" "github.com/kolo/xmlrpc" ) // API information. const ( APIBaseURL = "https://api.domrobot.com/xmlrpc/" APISandboxBaseURL = "https://api.ote.domrobot.com/xmlrpc/" APILanguage = "en" ) // Client manages communication with INWX API. type Client struct { // HTTP client used to communicate with the INWX API. RPCClient *xmlrpc.Client // API username and password username string password string lang string common service // Reuse a single struct instead of allocating one for each service on the heap. // Services used for communicating with the API Account *AccountService Contacts *ContactService Dnssec *DNSSecService Domains *DomainService Nameservers *NameserverService } type service struct { client *Client } // ClientOptions Options of the API client. type ClientOptions struct { Sandbox bool // Language of the return message. (en/de/es) Lang string // Base URL for API requests (only for client testing purpose). BaseURL *url.URL } // Request The representation of an API request. type Request struct { ServiceMethod string Args map[string]any } // NewClient returns a new INWX API client. func NewClient(username, password string, opts *ClientOptions) *Client { baseURL := getBaseURL(opts).String() rpcClient, _ := xmlrpc.NewClient(baseURL, nil) client := &Client{ RPCClient: rpcClient, username: username, password: password, lang: APILanguage, } if opts != nil && opts.Lang != "" { client.lang = opts.Lang } client.common.client = client client.Account = (*AccountService)(&client.common) client.Contacts = (*ContactService)(&client.common) client.Dnssec = (*DNSSecService)(&client.common) client.Domains = (*DomainService)(&client.common) client.Nameservers = (*NameserverService)(&client.common) return client } // NewRequest creates an API request. func (c *Client) NewRequest(serviceMethod string, args map[string]any) *Request { if args != nil { args["lang"] = APILanguage } return &Request{ServiceMethod: serviceMethod, Args: args} } // Do sends an API request and returns the API response. func (c *Client) Do(req *Request) (map[string]any, error) { var resp Response err := c.RPCClient.Call(req.ServiceMethod, req.Args, &resp) if err != nil { return nil, err } return resp.ResponseData, checkResponse(&resp) } // checkResponse checks the API response for errors, and returns them if present. func checkResponse(r *Response) error { if c := r.Code; c >= 1000 && c <= 1500 { return nil } return &ErrorResponse{Code: r.Code, Message: r.Message, Reason: r.Reason, ReasonCode: r.ReasonCode} } func getBaseURL(opts *ClientOptions) *url.URL { var useSandbox bool if opts != nil { useSandbox = opts.Sandbox } var baseURL *url.URL if useSandbox { baseURL, _ = url.Parse(APISandboxBaseURL) } else { baseURL, _ = url.Parse(APIBaseURL) } if opts != nil && opts.BaseURL != nil { baseURL = opts.BaseURL } return baseURL } goinwx-0.12.0/nameserver.go000066400000000000000000000233321511207150100155770ustar00rootroot00000000000000package goinwx import ( "errors" "fmt" "time" "github.com/fatih/structs" "github.com/go-viper/mapstructure/v2" ) const ( methodNameserverCheck = "nameserver.check" methodNameserverCreate = "nameserver.create" methodNameserverCreateRecord = "nameserver.createRecord" methodNameserverDelete = "nameserver.delete" methodNameserverDeleteRecord = "nameserver.deleteRecord" methodNameserverInfo = "nameserver.info" methodNameserverList = "nameserver.list" methodNameserverUpdate = "nameserver.update" methodNameserverUpdateRecord = "nameserver.updateRecord" ) // NameserverService API access to Nameservers. type NameserverService service // Check checks a domain on nameservers. func (s *NameserverService) Check(domain string, nameservers []string) (*NameserverCheckResponse, error) { req := s.client.NewRequest(methodNameserverCheck, map[string]any{ "domain": domain, "ns": nameservers, }) resp, err := s.client.Do(req) if err != nil { return nil, err } var result NameserverCheckResponse err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // Info gets information. func (s *NameserverService) Info(request *NameserverInfoRequest) (*NameserverInfoResponse, error) { req := s.client.NewRequest(methodNameserverInfo, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return nil, err } result := NameserverInfoResponse{} err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // List lists nameservers for a domain. // // Deprecated: use ListWithParams instead. func (s *NameserverService) List(domain string) (*NameserverListResponse, error) { requestMap := map[string]any{ "domain": "*", "wide": 2, } if domain != "" { requestMap["domain"] = domain } req := s.client.NewRequest(methodNameserverList, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } result := NameserverListResponse{} err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // ListWithParams lists nameservers for a domain. func (s *NameserverService) ListWithParams(request *NameserverListRequest) (*NameserverListResponse, error) { if request == nil { return nil, errors.New("request can't be nil") } requestMap := structs.Map(request) requestMap["wide"] = "2" req := s.client.NewRequest(methodNameserverList, requestMap) resp, err := s.client.Do(req) if err != nil { return nil, err } result := NameserverListResponse{} err = mapstructure.Decode(resp, &result) if err != nil { return nil, err } return &result, nil } // Create creates a nameserver. func (s *NameserverService) Create(request *NameserverCreateRequest) (int, error) { req := s.client.NewRequest(methodNameserverCreate, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return 0, err } var result map[string]int err = mapstructure.Decode(resp, &result) if err != nil { return 0, err } return result["roId"], nil } // CreateRecord creates a DNS record. func (s *NameserverService) CreateRecord(request *NameserverRecordRequest) (string, error) { req := s.client.NewRequest(methodNameserverCreateRecord, structs.Map(request)) resp, err := s.client.Do(req) if err != nil { return "", err } var result map[string]string err = mapstructure.Decode(resp, &result) if err != nil { return "", err } return result["id"], nil } // UpdateRecord updates a DNS record. func (s *NameserverService) UpdateRecord(recID string, request *NameserverRecordRequest) error { if request == nil { return errors.New("request can't be nil") } requestMap := structs.Map(request) requestMap["id"] = recID req := s.client.NewRequest(methodNameserverUpdateRecord, requestMap) _, err := s.client.Do(req) if err != nil { return err } return nil } // DeleteRecord deletes a DNS record. func (s *NameserverService) DeleteRecord(recID string) error { req := s.client.NewRequest(methodNameserverDeleteRecord, map[string]any{ "id": recID, }) _, err := s.client.Do(req) if err != nil { return err } return nil } // FindRecordByID search a DNS record by ID. func (s *NameserverService) FindRecordByID(recID string) (*NameserverRecord, *NameserverDomain, error) { listResp, err := s.client.Nameservers.ListWithParams(&NameserverListRequest{}) if err != nil { return nil, nil, err } for _, domainItem := range listResp.Domains { resp, err := s.client.Nameservers.Info(&NameserverInfoRequest{RoID: domainItem.RoID}) if err != nil { return nil, nil, err } for _, record := range resp.Records { if record.ID == recID { return &record, &domainItem, nil } } } return nil, nil, fmt.Errorf("couldn't find INWX Record for id %s", recID) } // NameserverCheckResponse API model. type NameserverCheckResponse struct { Details []string `mapstructure:"details"` Status string `mapstructure:"status"` } // NameserverRecordRequest API model. type NameserverRecordRequest struct { RoID int `structs:"roId,omitempty"` Domain string `structs:"domain,omitempty"` Type string `structs:"type"` Content string `structs:"content"` Name string `structs:"name,omitempty"` TTL int `structs:"ttl,omitempty"` Priority int `structs:"prio,omitempty"` URLAppend bool `structs:"urlAppend,omitempty"` URLRedirectType string `structs:"urlRedirectType,omitempty"` URLRedirectTitle string `structs:"urlRedirectTitle,omitempty"` URLRedirectDescription string `structs:"urlRedirectDescription,omitempty"` URLRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` URLRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` } // NameserverCreateRequest API model. type NameserverCreateRequest struct { Domain string `structs:"domain"` Type string `structs:"type"` Nameservers []string `structs:"ns,omitempty"` MasterIP string `structs:"masterIp,omitempty"` Web string `structs:"web,omitempty"` Mail string `structs:"mail,omitempty"` SoaEmail string `structs:"soaEmail,omitempty"` URLRedirectType string `structs:"urlRedirectType,omitempty"` URLRedirectTitle string `structs:"urlRedirectTitle,omitempty"` URLRedirectDescription string `structs:"urlRedirectDescription,omitempty"` URLRedirectFavIcon string `structs:"urlRedirectFavIcon,omitempty"` URLRedirectKeywords string `structs:"urlRedirectKeywords,omitempty"` Testing bool `structs:"testing,omitempty"` } // NameserverInfoRequest API model. type NameserverInfoRequest struct { Domain string `structs:"domain,omitempty"` RoID int `structs:"roId,omitempty"` RecordID string `structs:"recordId,omitempty"` Type string `structs:"type,omitempty"` Name string `structs:"name,omitempty"` Content string `structs:"content,omitempty"` TTL int `structs:"ttl,omitempty"` Priority int `structs:"prio,omitempty"` } // NamserverInfoResponse API model. // // Deprecated: Use NameserverInfoResponse instead. type NamserverInfoResponse = NameserverInfoResponse // NameserverInfoResponse API model. type NameserverInfoResponse struct { RoID int `mapstructure:"roId"` Domain string `mapstructure:"domain"` Type string `mapstructure:"type"` MasterIP string `mapstructure:"masterIp"` LastZoneCheck time.Time `mapstructure:"lastZoneCheck"` SlaveDNS []SlaveInfo `mapstructure:"slaveDns"` SOASerial string `mapstructure:"SOAserial"` Count int `mapstructure:"count"` Records []NameserverRecord `mapstructure:"record"` } // SlaveInfo API model. type SlaveInfo struct { Name string `mapstructure:"name"` IP string `mapstructure:"ip"` } // NameserverRecord API model. type NameserverRecord struct { ID string `mapstructure:"id"` Name string `mapstructure:"name"` Type string `mapstructure:"type"` Content string `mapstructure:"content"` TTL int `mapstructure:"TTL"` Priority int `mapstructure:"prio"` URLAppend bool `mapstructure:"urlAppend,omitempty"` URLRedirectType string `mapstructure:"urlRedirectType"` URLRedirectTitle string `mapstructure:"urlRedirectTitle"` URLRedirectDescription string `mapstructure:"urlRedirectDescription"` URLRedirectKeywords string `mapstructure:"urlRedirectKeywords"` URLRedirectFavIcon string `mapstructure:"urlRedirectFavIcon"` } // NameserverListRequest API model. type NameserverListRequest struct { Domain string `structs:"domain,omitempty"` Wide int `structs:"wide,omitempty"` Page int `structs:"page,omitempty"` PageLimit int `structs:"pagelimit,omitempty"` } // NamserverListResponse API model. // // Deprecated: Use NameserverListResponse instead. type NamserverListResponse = NameserverListResponse // NameserverListResponse API model. type NameserverListResponse struct { Count int `mapstructure:"count"` Domains []NameserverDomain `mapstructure:"domains"` } // NameserverDomain API model. type NameserverDomain struct { RoID int `mapstructure:"roId"` Domain string `mapstructure:"domain"` Type string `mapstructure:"type"` MasterIP string `mapstructure:"masterIp"` Mail string `mapstructure:"mail"` Web string `mapstructure:"web"` URL string `mapstructure:"url"` Ipv4 string `mapstructure:"ipv4"` Ipv6 string `mapstructure:"ipv6"` } goinwx-0.12.0/response.go000066400000000000000000000015101511207150100152600ustar00rootroot00000000000000package goinwx import "fmt" // Response is a INWX API response. This wraps the standard http.Response returned from INWX. type Response struct { Code int `xmlrpc:"code"` Message string `xmlrpc:"msg"` ReasonCode string `xmlrpc:"reasonCode"` Reason string `xmlrpc:"reason"` ResponseData map[string]any `xmlrpc:"resData"` } // An ErrorResponse reports the error caused by an API request. type ErrorResponse struct { Code int `xmlrpc:"code"` Message string `xmlrpc:"msg"` ReasonCode string `xmlrpc:"reasonCode"` Reason string `xmlrpc:"reason"` } func (r *ErrorResponse) Error() string { if r.Reason != "" { return fmt.Sprintf("(%d) %s. Reason: (%s) %s", r.Code, r.Message, r.ReasonCode, r.Reason) } return fmt.Sprintf("(%d) %s", r.Code, r.Message) }