修复编译错误

This commit is contained in:
CN-JS-HuiBai
2026-04-15 19:29:28 +08:00
parent 7a3126fc85
commit 13cbccafcb
5 changed files with 341 additions and 5 deletions

332
common/dnspod/provider.go Normal file
View File

@@ -0,0 +1,332 @@
package dnspod
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/libdns/libdns"
E "github.com/sagernet/sing/common/exceptions"
)
const defaultAPIEndpoint = "https://dnsapi.cn"
type Provider struct {
APIToken string
APIEndpoint string
HTTPClient *http.Client
}
type apiStatus struct {
Code string `json:"code"`
Message string `json:"message"`
}
type apiRecord struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
TTL string `json:"ttl"`
Value string `json:"value"`
}
type createRecordResponse struct {
Status apiStatus `json:"status"`
Record struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"record"`
}
type listRecordsResponse struct {
Status apiStatus `json:"status"`
Records []apiRecord `json:"records"`
}
func (p *Provider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
records, err := p.listRecords(ctx, normalizeZone(zone), "", "")
if err != nil {
return nil, err
}
results := make([]libdns.Record, 0, len(records))
for _, record := range records {
results = append(results, record.toLibdnsRecord())
}
return results, nil
}
func (p *Provider) AppendRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
zone = normalizeZone(zone)
if zone == "" {
return nil, E.New("DNSPod zone is empty")
}
if strings.TrimSpace(p.APIToken) == "" {
return nil, E.New("DNSPod API token is empty")
}
created := make([]libdns.Record, 0, len(records))
for _, record := range records {
requestRecord, err := normalizeInputRecord(record)
if err != nil {
return created, err
}
params := url.Values{
"domain": []string{zone},
"sub_domain": []string{requestRecord.Name},
"record_type": []string{requestRecord.Type},
"record_line": []string{"默认"},
"value": []string{requestRecord.Value},
}
if requestRecord.TTL > 0 {
params.Set("ttl", strconv.FormatInt(int64(requestRecord.TTL/time.Second), 10))
}
var response createRecordResponse
err = p.doForm(ctx, "Record.Create", params, &response)
if err != nil {
if requestRecord.Type == "TXT" && strings.Contains(err.Error(), "104") {
existing, listErr := p.listRecords(ctx, zone, requestRecord.Name, requestRecord.Type)
if listErr != nil {
return created, err
}
for _, candidate := range existing {
if requestRecord.matches(candidate) {
created = append(created, candidate.toLibdnsRecord())
err = nil
break
}
}
}
if err != nil {
return created, err
}
continue
}
requestRecord.ID = response.Record.ID
created = append(created, requestRecord.toLibdnsRecord())
}
return created, nil
}
func (p *Provider) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
zone = normalizeZone(zone)
if zone == "" {
return nil, E.New("DNSPod zone is empty")
}
if strings.TrimSpace(p.APIToken) == "" {
return nil, E.New("DNSPod API token is empty")
}
deleted := make([]libdns.Record, 0, len(records))
for _, record := range records {
requestRecord, err := normalizeInputRecord(record)
if err != nil {
return deleted, err
}
candidates, err := p.listRecords(ctx, zone, requestRecord.Name, requestRecord.Type)
if err != nil {
return deleted, err
}
for _, candidate := range candidates {
if !requestRecord.matches(candidate) {
continue
}
err = p.doForm(ctx, "Record.Remove", url.Values{
"domain": []string{zone},
"record_id": []string{candidate.ID},
}, nil)
if err != nil {
return deleted, err
}
deleted = append(deleted, candidate.toLibdnsRecord())
}
}
return deleted, nil
}
func (p *Provider) listRecords(ctx context.Context, zone, subDomain, recordType string) ([]apiRecord, error) {
params := url.Values{
"domain": []string{zone},
}
if subDomain != "" {
params.Set("sub_domain", subDomain)
}
if recordType != "" {
params.Set("record_type", recordType)
}
var response listRecordsResponse
err := p.doForm(ctx, "Record.List", params, &response)
if err != nil {
return nil, err
}
return response.Records, nil
}
func (p *Provider) doForm(ctx context.Context, action string, params url.Values, target any) error {
endpoint := strings.TrimRight(strings.TrimSpace(p.APIEndpoint), "/")
if endpoint == "" {
endpoint = defaultAPIEndpoint
}
body := url.Values{
"login_token": []string{strings.TrimSpace(p.APIToken)},
"format": []string{"json"},
}
for key, values := range params {
for _, value := range values {
body.Add(key, value)
}
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint+"/"+action, strings.NewReader(body.Encode()))
if err != nil {
return err
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := p.HTTPClient
if client == nil {
client = &http.Client{Timeout: 30 * time.Second}
}
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return E.New("DNSPod ", action, " failed: HTTP ", response.StatusCode)
}
data, err := io.ReadAll(response.Body)
if err != nil {
return err
}
if target == nil {
var wrapper struct {
Status apiStatus `json:"status"`
}
if err = json.Unmarshal(data, &wrapper); err != nil {
return err
}
if wrapper.Status.Code != "1" {
return E.New("DNSPod ", action, " failed: ", wrapper.Status.Code, " ", strings.TrimSpace(wrapper.Status.Message))
}
return nil
}
if err = json.Unmarshal(data, target); err != nil {
return err
}
switch result := target.(type) {
case *createRecordResponse:
if result.Status.Code != "1" {
return E.New("DNSPod ", action, " failed: ", result.Status.Code, " ", strings.TrimSpace(result.Status.Message))
}
case *listRecordsResponse:
if result.Status.Code != "1" {
return E.New("DNSPod ", action, " failed: ", result.Status.Code, " ", strings.TrimSpace(result.Status.Message))
}
}
return nil
}
func normalizeZone(zone string) string {
return strings.TrimSuffix(strings.TrimSpace(zone), ".")
}
func normalizeRecordName(name string) string {
name = strings.TrimSpace(name)
if name == "" {
return "@"
}
return strings.TrimSuffix(name, ".")
}
func normalizeRecordValue(record libdns.Record) string {
switch typed := record.(type) {
case libdns.TXT:
return typed.Text
case *libdns.TXT:
return typed.Text
default:
return record.RR().Data
}
}
type normalizedRecord struct {
ID string
Name string
Type string
TTL time.Duration
Value string
}
func normalizeInputRecord(record libdns.Record) (normalizedRecord, error) {
rr := record.RR()
name := normalizeRecordName(rr.Name)
if name == "" {
return normalizedRecord{}, E.New("DNSPod record name is empty")
}
recordType := strings.ToUpper(strings.TrimSpace(rr.Type))
if recordType == "" {
return normalizedRecord{}, E.New("DNSPod record type is empty")
}
return normalizedRecord{
Name: name,
Type: recordType,
TTL: rr.TTL,
Value: normalizeRecordValue(record),
}, nil
}
func (r normalizedRecord) matches(candidate apiRecord) bool {
if normalizeRecordName(candidate.Name) != r.Name {
return false
}
if r.Type != "" && !strings.EqualFold(candidate.Type, r.Type) {
return false
}
if r.Value != "" && candidate.Value != r.Value {
return false
}
if r.TTL > 0 {
candidateTTL, _ := strconv.ParseInt(candidate.TTL, 10, 64)
if time.Duration(candidateTTL)*time.Second != r.TTL {
return false
}
}
return true
}
func (r normalizedRecord) toLibdnsRecord() libdns.Record {
record := apiRecord{
ID: r.ID,
Name: r.Name,
Type: r.Type,
TTL: strconv.FormatInt(int64(r.TTL/time.Second), 10),
Value: r.Value,
}
return record.toLibdnsRecord()
}
func (r apiRecord) toLibdnsRecord() libdns.Record {
ttlSeconds, _ := strconv.ParseInt(strings.TrimSpace(r.TTL), 10, 64)
ttl := time.Duration(ttlSeconds) * time.Second
name := normalizeRecordName(r.Name)
recordType := strings.ToUpper(strings.TrimSpace(r.Type))
if recordType == "TXT" {
return libdns.TXT{
Name: name,
TTL: ttl,
Text: r.Value,
}
}
rr := libdns.RR{
Name: name,
TTL: ttl,
Type: recordType,
Data: r.Value,
}
parsed, err := rr.Parse()
if err != nil {
return rr
}
return parsed
}

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
boxdnspod "github.com/sagernet/sing-box/common/dnspod"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@@ -17,7 +18,6 @@ import (
"github.com/libdns/acmedns" "github.com/libdns/acmedns"
"github.com/libdns/alidns" "github.com/libdns/alidns"
"github.com/libdns/cloudflare" "github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/tencentcloud" "github.com/libdns/tencentcloud"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
@@ -106,7 +106,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
Region: dnsOptions.TencentCloudOptions.Region, Region: dnsOptions.TencentCloudOptions.Region,
} }
case C.DNSProviderDNSPod: case C.DNSProviderDNSPod:
solver.DNSProvider = &dnspod.Provider{ solver.DNSProvider = &boxdnspod.Provider{
APIToken: dnsOptions.DNSPodOptions.APIToken, APIToken: dnsOptions.DNSPodOptions.APIToken,
} }
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS:

1
go.mod
View File

@@ -20,7 +20,6 @@ require (
github.com/libdns/acmedns v0.5.0 github.com/libdns/acmedns v0.5.0
github.com/libdns/alidns v1.0.6 github.com/libdns/alidns v1.0.6
github.com/libdns/cloudflare v0.2.2 github.com/libdns/cloudflare v0.2.2
github.com/libdns/dnspod v0.0.3
github.com/libdns/libdns v1.1.1 github.com/libdns/libdns v1.1.1
github.com/libdns/tencentcloud v1.4.3 github.com/libdns/tencentcloud v1.4.3
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible

5
go.sum
View File

@@ -124,8 +124,12 @@ github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM=
github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec= github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI= github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
github.com/libdns/dnspod v0.0.3/go.mod h1:XLnqMmK7QlLPEbHwcOxbRvlzRvDgaaUlthRNFOPjXPI=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAzAlvc=
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
@@ -142,6 +146,7 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE= github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE=
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=

View File

@@ -17,6 +17,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/certificate" "github.com/sagernet/sing-box/adapter/certificate"
boxdnspod "github.com/sagernet/sing-box/common/dnspod"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
boxtls "github.com/sagernet/sing-box/common/tls" boxtls "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@@ -30,7 +31,6 @@ import (
"github.com/caddyserver/zerossl" "github.com/caddyserver/zerossl"
"github.com/libdns/alidns" "github.com/libdns/alidns"
"github.com/libdns/cloudflare" "github.com/libdns/cloudflare"
"github.com/libdns/dnspod"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/libdns/tencentcloud" "github.com/libdns/tencentcloud"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
@@ -250,7 +250,7 @@ func newDNSSolver(dnsOptions *option.ACMEProviderDNS01ChallengeOptions, logger *
Region: dnsOptions.TencentCloudOptions.Region, Region: dnsOptions.TencentCloudOptions.Region,
} }
case C.DNSProviderDNSPod: case C.DNSProviderDNSPod:
solver.DNSProvider = &dnspod.Provider{ solver.DNSProvider = &boxdnspod.Provider{
APIToken: dnsOptions.DNSPodOptions.APIToken, APIToken: dnsOptions.DNSPodOptions.APIToken,
} }
case C.DNSProviderACMEDNS: case C.DNSProviderACMEDNS: