98 lines
2.6 KiB
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
|
||
|
}
|