ncddns/providers/porkbun/porkbun.go

98 lines
2.6 KiB
Go

package porkbun
import (
"bytes"
"encoding/json"
"fmt"
"ncddns/mod"
"net/http"
"strings"
)
const (
baseURL = "https://porkbun.com/api/json/v3"
retrieve = "/dns/retrieve/"
edit = "/dns/edit/"
keySplitter = ":"
)
type Client struct {
}
func (Client) retrieve(domain string, keys Request) ([]Record, error) {
url := baseURL + retrieve + domain
mod.Verboseln("POST", url)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(mod.MustJSON(keys)))
if err != nil {
return nil, fmt.Errorf("POST retrieve: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("non-ok status code [%d]: %s", resp.StatusCode, mod.TryBody(resp.Body))
}
res := retrieveResponse{}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, fmt.Errorf("json: %w", err)
}
if res.Status != "SUCCESS" {
return nil, fmt.Errorf("non-SUCCESS status: %s", res.Status)
}
return res.Records, nil
}
func (Client) edit(req EditRequest, id, domain string) error {
url := baseURL + edit + domain + "/" + id
mod.Verboseln("POST", url)
resp, err := http.Post(url, "application/json", bytes.NewReader(mod.MustJSON(req)))
if err != nil {
return fmt.Errorf("POST edit: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non-ok status code [%d]: %s", resp.StatusCode, mod.TryBody(resp.Body))
}
resp.Body.Close()
return nil
}
func (c Client) IPSet(reqs []mod.IPSetRequest) error {
keyParts := strings.Split(reqs[0].Password, keySplitter)
if len(keyParts) != 2 {
panic(fmt.Sprintf("non-2 length split for password [%s]", reqs[0].Password))
}
keys := Request{
APIKey: keyParts[0],
Secret: keyParts[1],
}
var err error
domainRecords := map[string][]Record{}
for _, req := range reqs {
records, ok := domainRecords[req.Domain]
if !ok {
records, err = c.retrieve(req.Domain, keys)
if err != nil {
return fmt.Errorf("[%s]: retrieve: %w", req.Domain, err)
}
domainRecords[req.Domain] = records
}
for _, record := range records {
if record.Type != "A" && record.Type != "AAAA" {
mod.Verbosefln("Skipping [%s/%s/%s] due to type [%s]", record.IP, record.Name, record.IP, record.Type)
continue
}
mod.Verbosefln("Current IP for [%s/%s]: [%s], want [%s]", req.Domain, record.Type, record.IP, req.IP)
if req.IP == record.IP {
mod.Verboseln("Skipping...")
continue
}
edit := EditRequest{}.FromRecord(record, keys)
edit.IP = req.IP
if err := c.edit(edit, record.ID, req.Domain); err != nil {
return fmt.Errorf("[%s]: edit: %w", req.Domain, err)
}
}
}
return nil
}