package cloudflare import ( "bytes" "encoding/json" "fmt" "io" "net/http" "sync" "time" "agent-wdd/log" ) const ( // CloudflareAPIBaseURL is the base URL for Cloudflare API CloudflareAPIBaseURL = "https://api.cloudflare.com/client/v4" // DefaultTimeout is the default timeout for HTTP requests DefaultTimeout = 5 * time.Second ) // CloudflareClient represents a client for interacting with Cloudflare API type CloudflareClient struct { apiToken string baseURL string client *http.Client userAgent string dnsNameToIDMap map[string]string // 域名到ID的映射 } var ( instance *CloudflareClient once sync.Once ) // Response is the general response structure from Cloudflare API type Response struct { Result interface{} `json:"result"` ResultInfo *ResultInfo `json:"result_info,omitempty"` Success bool `json:"success"` Errors []Error `json:"errors"` Messages []interface{} `json:"messages"` } // ResultInfo contains pagination information type ResultInfo struct { Page int `json:"page"` PerPage int `json:"per_page"` TotalPages int `json:"total_pages"` Count int `json:"count"` TotalCount int `json:"total_count"` } // Error represents an error returned by the Cloudflare API type Error struct { Code int `json:"code"` Message string `json:"message"` ErrorChain []Error `json:"error_chain,omitempty"` } // NewClient creates a new instance of CloudflareClient with the given API token func NewClient(apiToken string) *CloudflareClient { log.Debug("Creating new Cloudflare client") return &CloudflareClient{ apiToken: apiToken, baseURL: CloudflareAPIBaseURL, client: &http.Client{Timeout: DefaultTimeout}, userAgent: "WddSuperAgent/1.0", } } // GetInstance returns the singleton instance of CloudflareClient func GetInstance(apiToken string) *CloudflareClient { if instance == nil { once.Do(func() { instance = NewClient(apiToken) log.Info("Cloudflare client singleton instance created") }) } return instance } // SetAPIToken updates the API token used for authentication func (c *CloudflareClient) SetAPIToken(apiToken string) { c.apiToken = apiToken log.Info("Cloudflare API token updated") } // SetTimeout updates the HTTP client timeout func (c *CloudflareClient) SetTimeout(timeout time.Duration) { c.client.Timeout = timeout log.Info("Cloudflare client timeout updated to %v", timeout) } // doRequest performs an HTTP request with the given method, URL, and body func (c *CloudflareClient) doRequest(method, url string, body interface{}) (*Response, error) { log.Debug("Performing %s request to %s", method, url) var reqBody io.Reader if body != nil { jsonBody, err := json.Marshal(body) if err != nil { log.Error("Failed to marshal request body: %v", err) return nil, fmt.Errorf("failed to marshal request body: %w", err) } reqBody = bytes.NewBuffer(jsonBody) } req, err := http.NewRequest(method, url, reqBody) if err != nil { log.Error("Failed to create HTTP request: %v", err) return nil, fmt.Errorf("failed to create HTTP request: %w", err) } // Set headers req.Header.Set("Authorization", "Bearer "+c.apiToken) req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", c.userAgent) // Perform request resp, err := c.client.Do(req) if err != nil { log.Error("Failed to execute HTTP request: %v", err) return nil, fmt.Errorf("failed to execute HTTP request: %w", err) } defer resp.Body.Close() // Read response body respBody, err := io.ReadAll(resp.Body) if err != nil { log.Error("Failed to read response body: %v", err) return nil, fmt.Errorf("failed to read response body: %w", err) } // Parse response var cfResp Response if err := json.Unmarshal(respBody, &cfResp); err != nil { log.Error("Failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %w", err) } // Check for API errors if !cfResp.Success { errorMsg := "Cloudflare API error" if len(cfResp.Errors) > 0 { errorMsg = fmt.Sprintf("%s: %s (code: %d)", errorMsg, cfResp.Errors[0].Message, cfResp.Errors[0].Code) } log.Error(errorMsg) return &cfResp, fmt.Errorf(errorMsg) } log.Debug("Request completed successfully") return &cfResp, nil } // GetDNSNameToIDMap 获取域名到ID的映射 func (c *CloudflareClient) GetDNSNameToIDMap(zoneID string) map[string]string { // 如果dnsNameToIDMap为空,则获取所有域名到ID的映射 if c.dnsNameToIDMap == nil { c.ListDNSRecords(zoneID, nil) } return c.dnsNameToIDMap }