From 4d78d8a6ff122207aa02ff2e2c566222c78f9b9c Mon Sep 17 00:00:00 2001 From: Timothy Date: Tue, 2 Mar 2021 17:31:26 +0800 Subject: [PATCH] feat: add notify manager --- notify/common.go | 55 ++++++++ template.go => notify/email.go | 40 +++++- notify/manager.go | 34 +++++ notify/slack.go | 78 +++++++++++ notify/telegram.go | 75 +++++++++++ utils.go | 231 +++------------------------------ 6 files changed, 297 insertions(+), 216 deletions(-) create mode 100644 notify/common.go rename template.go => notify/email.go (88%) create mode 100644 notify/manager.go create mode 100644 notify/slack.go create mode 100644 notify/telegram.go diff --git a/notify/common.go b/notify/common.go new file mode 100644 index 0000000..740e9ea --- /dev/null +++ b/notify/common.go @@ -0,0 +1,55 @@ +package notify + +import ( + "bytes" + "log" + "net/http" + "text/template" + + "github.com/TimothyYe/godns" + "golang.org/x/net/proxy" +) + +// GetHttpClient creates the HTTP client and return it +func GetHttpClient(conf *godns.Settings, useProxy bool) *http.Client { + client := &http.Client{} + + if useProxy && conf.Socks5Proxy != "" { + log.Println("use socks5 proxy:" + conf.Socks5Proxy) + dialer, err := proxy.SOCKS5("tcp", conf.Socks5Proxy, nil, proxy.Direct) + if err != nil { + log.Println("can't connect to the proxy:", err) + return nil + } + + httpTransport := &http.Transport{} + client.Transport = httpTransport + httpTransport.Dial = dialer.Dial + } + + return client +} + +func buildTemplate(currentIP, domain string, tplsrc string) string { + t := template.New("notification template") + if _, err := t.Parse(tplsrc); err != nil { + log.Println("Failed to parse template") + return "" + } + + data := struct { + CurrentIP string + Domain string + }{ + currentIP, + domain, + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, data); err != nil { + log.Println(err.Error()) + return "" + } + + return tpl.String() +} diff --git a/template.go b/notify/email.go similarity index 88% rename from template.go rename to notify/email.go index a7b2ceb..9952e87 100644 --- a/template.go +++ b/notify/email.go @@ -1,4 +1,42 @@ -package godns +package notify + +import ( + "log" + + "github.com/TimothyYe/godns" + "gopkg.in/gomail.v2" +) + +type EmailNotify struct { + conf *godns.Settings +} + +func (n *EmailNotify) Send(domain, currentIP string) error { + if !n.conf.Notify.Mail.Enabled { + return nil + } + log.Print("Sending notification to:", n.conf.Notify.Mail.SendTo) + m := gomail.NewMessage() + + m.SetHeader("From", n.conf.Notify.Mail.SMTPUsername) + m.SetHeader("To", n.conf.Notify.Mail.SendTo) + m.SetHeader("Subject", "GoDNS Notification") + log.Println("currentIP:", currentIP) + log.Println("domain:", domain) + m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate)) + + d := gomail.NewDialer( + n.conf.Notify.Mail.SMTPServer, + n.conf.Notify.Mail.SMTPPort, + n.conf.Notify.Mail.SMTPUsername, + n.conf.Notify.Mail.SMTPPassword) + + // Send the email config by sendlist . + if err := d.DialAndSend(m); err != nil { + return err + } + return nil +} var mailTemplate = ` diff --git a/notify/manager.go b/notify/manager.go new file mode 100644 index 0000000..38a1584 --- /dev/null +++ b/notify/manager.go @@ -0,0 +1,34 @@ +package notify + +import ( + "sync" + + "github.com/TimothyYe/godns" +) + +var ( + instance *notifyManager + once sync.Once +) + +type INotify interface { + Send(conf *godns.Settings, domain, currentIP string) error +} + +type notifyManager struct { + notifications map[string]*INotify +} + +func GetNotifyManager(conf *godns.Settings) *notifyManager { + once.Do(func() { + instance = ¬ifyManager{ + notifications: initNotifications(conf), + } + }) + + return instance +} + +func initNotifications(conf *godns.Settings) map[string]*INotify { + return map[string]*INotify{} +} diff --git a/notify/slack.go b/notify/slack.go new file mode 100644 index 0000000..dac0ad7 --- /dev/null +++ b/notify/slack.go @@ -0,0 +1,78 @@ +package notify + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + + "github.com/TimothyYe/godns" +) + +type SlackNotify struct { + conf *godns.Settings +} + +func (n *SlackNotify) SendSlackNotify(domain, currentIP string) error { + if !n.conf.Notify.Slack.Enabled { + return nil + } + + if n.conf.Notify.Slack.BotApiToken == "" { + return errors.New("bot api token cannot be empty") + } + + if n.conf.Notify.Slack.Channel == "" { + return errors.New("channel cannot be empty") + } + client := GetHttpClient(n.conf, n.conf.Notify.Slack.UseProxy) + tpl := n.conf.Notify.Slack.MsgTemplate + if tpl == "" { + tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated" + } + + msg := buildTemplate(currentIP, domain, tpl) + + var response *http.Response + var err error + + formData := url.Values{ + "token": {n.conf.Notify.Slack.BotApiToken}, + "channel": {n.conf.Notify.Slack.Channel}, + "text": {msg}, + } + + response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData) + + if err != nil { + return err + } + + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + type ResponseParameters struct { + MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional + RetryAfter int `json:"retry_after"` // optional + } + type APIResponse struct { + Ok bool `json:"ok"` + Result json.RawMessage `json:"result"` + ErrorCode int `json:"error_code"` + Description string `json:"description"` + Parameters *ResponseParameters `json:"parameters"` + } + var resp APIResponse + err = json.Unmarshal(body, &resp) + if err != nil { + fmt.Println("error:", err) + return errors.New("failed to parse response") + } + if !resp.Ok { + return errors.New(resp.Description) + } + + return nil +} diff --git a/notify/telegram.go b/notify/telegram.go new file mode 100644 index 0000000..44cf414 --- /dev/null +++ b/notify/telegram.go @@ -0,0 +1,75 @@ +package notify + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + "github.com/TimothyYe/godns" +) + +type TelegramNotify struct { + conf *godns.Settings +} + +func (n *TelegramNotify) Send(domain, currentIP string) error { + if !n.conf.Notify.Telegram.Enabled { + return nil + } + + if n.conf.Notify.Telegram.BotApiKey == "" { + return errors.New("bot api key cannot be empty") + } + + if n.conf.Notify.Telegram.ChatId == "" { + return errors.New("chat id cannot be empty") + } + + client := GetHttpClient(n.conf, n.conf.Notify.Telegram.UseProxy) + tpl := n.conf.Notify.Telegram.MsgTemplate + if tpl == "" { + tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated" + } + + msg := buildTemplate(currentIP, domain, tpl) + reqURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s", + n.conf.Notify.Telegram.BotApiKey, + n.conf.Notify.Telegram.ChatId, + msg) + var response *http.Response + var err error + + response, err = client.Get(reqURL) + + if err != nil { + return err + } + + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + type ResponseParameters struct { + MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional + RetryAfter int `json:"retry_after"` // optional + } + type APIResponse struct { + Ok bool `json:"ok"` + Result json.RawMessage `json:"result"` + ErrorCode int `json:"error_code"` + Description string `json:"description"` + Parameters *ResponseParameters `json:"parameters"` + } + var resp APIResponse + err = json.Unmarshal(body, &resp) + if err != nil { + fmt.Println("error:", err) + return errors.New("failed to parse response") + } + if !resp.Ok { + return errors.New(resp.Description) + } + + return nil +} diff --git a/utils.go b/utils.go index bc248c8..6e38170 100644 --- a/utils.go +++ b/utils.go @@ -1,23 +1,16 @@ package godns import ( - "bytes" - "encoding/json" "errors" - "fmt" - "html/template" "io/ioutil" "log" "net" "net/http" - "net/url" "strings" dnsResolver "github.com/TimothyYe/godns/resolver" "github.com/miekg/dns" - "golang.org/x/net/proxy" - "gopkg.in/gomail.v2" ) var ( @@ -119,26 +112,6 @@ func isIPv4(ip string) bool { return strings.Count(ip, ":") < 2 } -// GetHttpClient creates the HTTP client and return it -func GetHttpClient(configuration *Settings, useProxy bool) *http.Client { - client := &http.Client{} - - if useProxy && configuration.Socks5Proxy != "" { - log.Println("use socks5 proxy:" + configuration.Socks5Proxy) - dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct) - if err != nil { - log.Println("can't connect to the proxy:", err) - return nil - } - - httpTransport := &http.Transport{} - client.Transport = httpTransport - httpTransport.Dial = dialer.Dial - } - - return client -} - //GetCurrentIP gets an IP from either internet or specific interface, depending on configuration func GetCurrentIP(configuration *Settings) (string, error) { var err error @@ -241,194 +214,22 @@ func CheckSettings(config *Settings) error { return nil } -// SendTelegramNotify sends notify if IP is changed -func SendTelegramNotify(configuration *Settings, domain, currentIP string) error { - if !configuration.Notify.Telegram.Enabled { - return nil - } - - if configuration.Notify.Telegram.BotApiKey == "" { - return errors.New("bot api key cannot be empty") - } - - if configuration.Notify.Telegram.ChatId == "" { - return errors.New("chat id cannot be empty") - } - - client := GetHttpClient(configuration, configuration.Notify.Telegram.UseProxy) - tpl := configuration.Notify.Telegram.MsgTemplate - if tpl == "" { - tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated" - } - - msg := buildTemplate(currentIP, domain, tpl) - reqURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s", - configuration.Notify.Telegram.BotApiKey, - configuration.Notify.Telegram.ChatId, - msg) - var response *http.Response - var err error - - response, err = client.Get(reqURL) - - if err != nil { - return err - } - - defer response.Body.Close() - - body, _ := ioutil.ReadAll(response.Body) - type ResponseParameters struct { - MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional - RetryAfter int `json:"retry_after"` // optional - } - type APIResponse struct { - Ok bool `json:"ok"` - Result json.RawMessage `json:"result"` - ErrorCode int `json:"error_code"` - Description string `json:"description"` - Parameters *ResponseParameters `json:"parameters"` - } - var resp APIResponse - err = json.Unmarshal(body, &resp) - if err != nil { - fmt.Println("error:", err) - return errors.New("failed to parse response") - } - if !resp.Ok { - return errors.New(resp.Description) - } - - return nil -} - -// SendMailNotify sends mail notify if IP is changed -func SendMailNotify(configuration *Settings, domain, currentIP string) error { - if !configuration.Notify.Mail.Enabled { - return nil - } - log.Print("Sending notification to:", configuration.Notify.Mail.SendTo) - m := gomail.NewMessage() - - m.SetHeader("From", configuration.Notify.Mail.SMTPUsername) - m.SetHeader("To", configuration.Notify.Mail.SendTo) - m.SetHeader("Subject", "GoDNS Notification") - log.Println("currentIP:", currentIP) - log.Println("domain:", domain) - m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate)) - - d := gomail.NewDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword) - - // Send the email config by sendlist . - if err := d.DialAndSend(m); err != nil { - return err - } - return nil -} - -// SendSlack sends slack if IP is changed -func SendSlackNotify(configuration *Settings, domain, currentIP string) error { - if !configuration.Notify.Slack.Enabled { - return nil - } - - if configuration.Notify.Slack.BotApiToken == "" { - return errors.New("bot api token cannot be empty") - } - - if configuration.Notify.Slack.Channel == "" { - return errors.New("channel cannot be empty") - } - client := GetHttpClient(configuration, configuration.Notify.Slack.UseProxy) - tpl := configuration.Notify.Slack.MsgTemplate - if tpl == "" { - tpl = "_Your IP address is changed to_\n\n*{{ .CurrentIP }}*\n\nDomain *{{ .Domain }}* is updated" - } - - msg := buildTemplate(currentIP, domain, tpl) - - var response *http.Response - var err error - - formData := url.Values{ - "token": {configuration.Notify.Slack.BotApiToken}, - "channel": {configuration.Notify.Slack.Channel}, - "text": {msg}, - } - - response, err = client.PostForm("https://slack.com/api/chat.postMessage", formData) - - if err != nil { - return err - } - - defer response.Body.Close() - - body, _ := ioutil.ReadAll(response.Body) - type ResponseParameters struct { - MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional - RetryAfter int `json:"retry_after"` // optional - } - type APIResponse struct { - Ok bool `json:"ok"` - Result json.RawMessage `json:"result"` - ErrorCode int `json:"error_code"` - Description string `json:"description"` - Parameters *ResponseParameters `json:"parameters"` - } - var resp APIResponse - err = json.Unmarshal(body, &resp) - if err != nil { - fmt.Println("error:", err) - return errors.New("failed to parse response") - } - if !resp.Ok { - return errors.New(resp.Description) - } - - return nil -} - -// SendNotify sends notify if IP is changed -func SendNotify(configuration *Settings, domain, currentIP string) error { - err := SendTelegramNotify(configuration, domain, currentIP) - if err != nil { - log.Println("Send telegram notification with error:", err.Error()) - } - err = SendMailNotify(configuration, domain, currentIP) - if err != nil { - log.Println("Send email notification with error:", err.Error()) - } - err = SendSlackNotify(configuration, domain, currentIP) - if err != nil { - log.Println("Send slack notification with error:", err.Error()) - } - return nil -} - -func buildTemplate(currentIP, domain string, tplsrc string) string { - t := template.New("notification template") - if _, err := t.Parse(tplsrc); err != nil { - log.Println("Failed to parse template") - return "" - } - - data := struct { - CurrentIP string - Domain string - }{ - currentIP, - domain, - } - - var tpl bytes.Buffer - if err := t.Execute(&tpl, data); err != nil { - log.Println(err.Error()) - return "" - } - - return tpl.String() -} +//// SendNotify sends notify if IP is changed +//func SendNotify(configuration *Settings, domain, currentIP string) error { +// err := SendTelegramNotify(configuration, domain, currentIP) +// if err != nil { +// log.Println("Send telegram notification with error:", err.Error()) +// } +// err = SendMailNotify(configuration, domain, currentIP) +// if err != nil { +// log.Println("Send email notification with error:", err.Error()) +// } +// err = SendSlackNotify(configuration, domain, currentIP) +// if err != nil { +// log.Println("Send slack notification with error:", err.Error()) +// } +// return nil +//} // ResolveDNS will query DNS for a given hostname. func ResolveDNS(hostname, resolver, ipType string) (string, error) {