mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:54:09 -04:00
207 lines
4.8 KiB
Go
207 lines
4.8 KiB
Go
package settings
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/qdm12/ddns-updater/internal/constants"
|
|
"github.com/qdm12/ddns-updater/internal/models"
|
|
"github.com/qdm12/ddns-updater/internal/regex"
|
|
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
|
)
|
|
|
|
type dnspod struct {
|
|
domain string
|
|
host string
|
|
ipVersion ipversion.IPVersion
|
|
token string
|
|
}
|
|
|
|
func NewDNSPod(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion,
|
|
_ regex.Matcher) (s Settings, err error) {
|
|
extraSettings := struct {
|
|
Token string `json:"token"`
|
|
}{}
|
|
if err := json.Unmarshal(data, &extraSettings); err != nil {
|
|
return nil, err
|
|
}
|
|
d := &dnspod{
|
|
domain: domain,
|
|
host: host,
|
|
ipVersion: ipVersion,
|
|
token: extraSettings.Token,
|
|
}
|
|
if err := d.isValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
func (d *dnspod) isValid() error {
|
|
if len(d.token) == 0 {
|
|
return ErrEmptyToken
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *dnspod) String() string {
|
|
return toString(d.domain, d.host, constants.DNSPOD, d.ipVersion)
|
|
}
|
|
|
|
func (d *dnspod) Domain() string {
|
|
return d.domain
|
|
}
|
|
|
|
func (d *dnspod) Host() string {
|
|
return d.host
|
|
}
|
|
|
|
func (d *dnspod) IPVersion() ipversion.IPVersion {
|
|
return d.ipVersion
|
|
}
|
|
|
|
func (d *dnspod) Proxied() bool {
|
|
return false
|
|
}
|
|
|
|
func (d *dnspod) BuildDomainName() string {
|
|
return buildDomainName(d.host, d.domain)
|
|
}
|
|
|
|
func (d *dnspod) HTML() models.HTMLRow {
|
|
return models.HTMLRow{
|
|
Domain: models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
|
|
Host: models.HTML(d.Host()),
|
|
Provider: "<a href=\"https://www.dnspod.cn/\">DNSPod</a>",
|
|
IPVersion: models.HTML(d.ipVersion),
|
|
}
|
|
}
|
|
|
|
func (d *dnspod) setHeaders(request *http.Request) {
|
|
setContentType(request, "application/x-www-form-urlencoded")
|
|
setAccept(request, "application/json")
|
|
setUserAgent(request)
|
|
}
|
|
|
|
func (d *dnspod) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
|
|
recordType := A
|
|
if ip.To4() == nil {
|
|
recordType = AAAA
|
|
}
|
|
u := url.URL{
|
|
Scheme: "https",
|
|
Host: "dnsapi.cn",
|
|
Path: "/Record.List",
|
|
}
|
|
|
|
values := url.Values{}
|
|
values.Set("login_token", d.token)
|
|
values.Set("format", "json")
|
|
values.Set("domain", d.domain)
|
|
values.Set("length", "200")
|
|
values.Set("sub_domain", d.host)
|
|
values.Set("record_type", recordType)
|
|
buffer := bytes.NewBufferString(values.Encode())
|
|
|
|
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d.setHeaders(request)
|
|
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%w: %d: %s",
|
|
ErrBadHTTPStatus, response.StatusCode, bodyToSingleLine(response.Body))
|
|
}
|
|
|
|
decoder := json.NewDecoder(response.Body)
|
|
var recordResp struct {
|
|
Records []struct {
|
|
ID string `json:"id"`
|
|
Value string `json:"value"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Line string `json:"line"`
|
|
} `json:"records"`
|
|
}
|
|
if err := decoder.Decode(&recordResp); err != nil {
|
|
return nil, fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
|
}
|
|
|
|
var recordID, recordLine string
|
|
for _, record := range recordResp.Records {
|
|
if record.Type == A && record.Name == d.host {
|
|
receivedIP := net.ParseIP(record.Value)
|
|
if ip.Equal(receivedIP) {
|
|
return ip, nil
|
|
}
|
|
recordID = record.ID
|
|
recordLine = record.Line
|
|
break
|
|
}
|
|
}
|
|
if len(recordID) == 0 {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
u.Path = "/Record.Ddns"
|
|
values = url.Values{}
|
|
values.Set("login_token", d.token)
|
|
values.Set("format", "json")
|
|
values.Set("domain", d.domain)
|
|
values.Set("record_id", recordID)
|
|
values.Set("value", ip.String())
|
|
values.Set("record_line", recordLine)
|
|
values.Set("sub_domain", d.host)
|
|
buffer = bytes.NewBufferString(values.Encode())
|
|
|
|
request, err = http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d.setHeaders(request)
|
|
|
|
response, err = client.Do(request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%w: %d: %s",
|
|
ErrBadHTTPStatus, response.StatusCode, bodyToSingleLine(response.Body))
|
|
}
|
|
|
|
decoder = json.NewDecoder(response.Body)
|
|
var ddnsResp struct {
|
|
Record struct {
|
|
ID int64 `json:"id"`
|
|
Value string `json:"value"`
|
|
Name string `json:"name"`
|
|
} `json:"record"`
|
|
}
|
|
if err := decoder.Decode(&ddnsResp); err != nil {
|
|
return nil, fmt.Errorf("%w: %s", ErrUnmarshalResponse, err)
|
|
}
|
|
|
|
ipStr := ddnsResp.Record.Value
|
|
receivedIP := net.ParseIP(ipStr)
|
|
if receivedIP == nil {
|
|
return nil, fmt.Errorf("%w: %s", ErrIPReceivedMalformed, ipStr)
|
|
} else if !ip.Equal(receivedIP) {
|
|
return nil, fmt.Errorf("%w: %s", ErrIPReceivedMismatch, receivedIP.String())
|
|
}
|
|
return ip, nil
|
|
}
|