mirror of
https://github.com/jeessy2/ddns-go.git
synced 2026-10-26 11:09:39 +08:00
* feat(webhook): 新增 #{timestamp} 变量支持
- 在 webhook 回调中添加 UTC+0 秒级时间戳变量 #{timestamp}
- 更新中英文文档中的变量表格与目录格式
- 优化 webhook/callback 表格排版
* docs: 添加微信推送 Webhook 配置示例
---------
Co-authored-by: rachel <rachel@localhost>
178 lines
4.6 KiB
Go
178 lines
4.6 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jeessy2/ddns-go/v6/util"
|
|
)
|
|
|
|
// Webhook Webhook
|
|
type Webhook struct {
|
|
WebhookURL string
|
|
WebhookRequestBody string
|
|
WebhookHeaders string
|
|
}
|
|
|
|
// updateStatusType 更新状态
|
|
type updateStatusType string
|
|
|
|
const (
|
|
// UpdatedNothing 未改变
|
|
UpdatedNothing updateStatusType = "未改变"
|
|
// UpdatedFailed 更新失败
|
|
UpdatedFailed = "失败"
|
|
// UpdatedSuccess 更新成功
|
|
UpdatedSuccess = "成功"
|
|
)
|
|
|
|
// 更新失败次数
|
|
var updatedFailedTimes = 0
|
|
|
|
// hasJSONPrefix returns true if the string starts with a JSON open brace.
|
|
func hasJSONPrefix(s string) bool {
|
|
return strings.HasPrefix(s, "{") || strings.HasPrefix(s, "[")
|
|
}
|
|
|
|
// ExecWebhook 添加或更新IPv4/IPv6记录, 返回是否有更新失败的
|
|
func ExecWebhook(domains *Domains, conf *Config) (v4Status updateStatusType, v6Status updateStatusType) {
|
|
v4Status = getDomainsStatus(domains.Ipv4Domains)
|
|
v6Status = getDomainsStatus(domains.Ipv6Domains)
|
|
|
|
if conf.WebhookURL != "" && (v4Status != UpdatedNothing || v6Status != UpdatedNothing) {
|
|
// 第3次失败才触发一次webhook
|
|
if v4Status == UpdatedFailed || v6Status == UpdatedFailed {
|
|
updatedFailedTimes++
|
|
if updatedFailedTimes != 3 {
|
|
util.Log("将不会触发Webhook, 仅在第 3 次失败时触发一次Webhook, 当前失败次数:%d", updatedFailedTimes)
|
|
return
|
|
}
|
|
} else {
|
|
updatedFailedTimes = 0
|
|
}
|
|
|
|
// 成功和失败都要触发webhook
|
|
method := "GET"
|
|
postPara := ""
|
|
timestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10)
|
|
contentType := "application/x-www-form-urlencoded"
|
|
if conf.WebhookRequestBody != "" {
|
|
method = "POST"
|
|
postPara = replacePara(domains, conf.WebhookRequestBody, v4Status, v6Status, timestamp)
|
|
if json.Valid([]byte(postPara)) {
|
|
contentType = "application/json"
|
|
} else if hasJSONPrefix(postPara) {
|
|
// 如果 RequestBody 的 JSON 无效但前缀为 JSON,提示无效
|
|
util.Log("Webhook中的 RequestBody JSON 无效")
|
|
}
|
|
}
|
|
requestURL := replacePara(domains, conf.WebhookURL, v4Status, v6Status, timestamp)
|
|
u, err := url.Parse(requestURL)
|
|
if err != nil {
|
|
util.Log("Webhook配置中的URL不正确")
|
|
return
|
|
}
|
|
|
|
q, _ := url.ParseQuery(u.RawQuery)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(method, u.String(), strings.NewReader(postPara))
|
|
if err != nil {
|
|
util.Log("Webhook调用失败! 异常信息:%s", err)
|
|
return
|
|
}
|
|
|
|
headers := extractHeaders(conf.WebhookHeaders)
|
|
for key, value := range headers {
|
|
req.Header.Add(key, value)
|
|
}
|
|
req.Header.Add("content-type", contentType)
|
|
|
|
clt := util.CreateHTTPClient()
|
|
resp, err := clt.Do(req)
|
|
body, err := util.GetHTTPResponseOrg(resp, err)
|
|
if err == nil {
|
|
util.Log("Webhook调用成功! 返回数据:%s", string(body))
|
|
} else {
|
|
util.Log("Webhook调用失败! 异常信息:%s", err)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// getDomainsStatus 获取域名状态
|
|
func getDomainsStatus(domains []*Domain) updateStatusType {
|
|
successNum := 0
|
|
for _, v46 := range domains {
|
|
switch v46.UpdateStatus {
|
|
case UpdatedFailed:
|
|
// 一个失败,全部失败
|
|
return UpdatedFailed
|
|
case UpdatedSuccess:
|
|
successNum++
|
|
}
|
|
}
|
|
|
|
if successNum > 0 {
|
|
// 迭代完成后一个成功,就成功
|
|
return UpdatedSuccess
|
|
}
|
|
return UpdatedNothing
|
|
}
|
|
|
|
// replacePara 替换参数
|
|
func replacePara(domains *Domains, orgPara string, ipv4Result updateStatusType, ipv6Result updateStatusType, timestamp string) string {
|
|
return strings.NewReplacer(
|
|
"#{ipv4Addr}", domains.Ipv4Addr,
|
|
"#{ipv4Result}", util.LogStr(string(ipv4Result)), // i18n
|
|
"#{ipv4Domains}", getDomainsStr(domains.Ipv4Domains),
|
|
"#{ipv6Addr}", domains.Ipv6Addr,
|
|
"#{ipv6Result}", util.LogStr(string(ipv6Result)), // i18n
|
|
"#{ipv6Domains}", getDomainsStr(domains.Ipv6Domains),
|
|
"#{timestamp}", timestamp,
|
|
).Replace(orgPara)
|
|
}
|
|
|
|
// getDomainsStr 用逗号分割域名
|
|
func getDomainsStr(domains []*Domain) string {
|
|
str := ""
|
|
for i, v46 := range domains {
|
|
str += v46.String()
|
|
if i != len(domains)-1 {
|
|
str += ","
|
|
}
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
// extractHeaders converts s into a map of headers.
|
|
//
|
|
// See also: https://github.com/appleboy/gorush/blob/v1.17.0/notify/feedback.go#L15
|
|
func extractHeaders(s string) map[string]string {
|
|
lines := util.SplitLines(s)
|
|
headers := make(map[string]string, len(lines))
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
util.Log("Webhook Header不正确: %s", line)
|
|
continue
|
|
}
|
|
|
|
k, v := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
|
headers[k] = v
|
|
}
|
|
|
|
return headers
|
|
}
|