1
0
mirror of https://github.com/taigrr/godns synced 2025-01-18 04:03:25 -08:00

feat: add notify manager

This commit is contained in:
Timothy
2021-03-02 17:31:26 +08:00
parent 5ad83edfe8
commit 4d78d8a6ff
6 changed files with 297 additions and 216 deletions

55
notify/common.go Normal file
View File

@@ -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()
}

143
notify/email.go Normal file
View File

@@ -0,0 +1,143 @@
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 = `
<html>
<body>
<div role="section">
<div style="background-color: #281557;">
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges">&nbsp;</td><td style="width: 600px" class="w560"><![endif]-->
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 10px;font-size: 1px;">&nbsp;</div>
</div>
</div>
<!--[if (mso)|(IE)]></td><td class="layout__edges">&nbsp;</td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="background-color: #281557;">
<div class="layout one-col" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-full-width" style="background-color: #281557;"><td class="layout__edges">&nbsp;</td><td style="width: 600px" class="w560"><![endif]-->
<div class="column" style="max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);text-align: left;color: #8e959c;font-size: 14px;line-height: 21px;font-family: sans-serif;">
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 50px;font-size: 1px;">&nbsp;</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;mso-text-raise: 4px;">
<h1 class="size-28" style="Margin-top: 0;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 24px;line-height: 32px;font-family: avenir,sans-serif;text-align: center;"
lang="x-size-28">
<span class="font-avenir">
<span style="color:#ffffff">Your IP address has been changed to</span>
</span>
</h1>
<h1 class="size-48" style="Margin-top: 20px;Margin-bottom: 0;font-style: normal;font-weight: normal;color: #000;font-size: 36px;line-height: 43px;font-family: avenir,sans-serif;text-align: center;"
lang="x-size-48">
<span class="font-avenir">
<strong>
<span style="color:#ffffff">{{ .CurrentIP }}</span>
</strong>
</span>
</h1>
<h2 class="size-28" style="Margin-top: 20px;Margin-bottom: 16px;font-style: normal;font-weight: normal;color: #e31212;font-size: 24px;line-height: 32px;font-family: Avenir,sans-serif;text-align: center;"
lang="x-size-28">
<font color="#ffffff">
<strong>Domain {{ .Domain }} is updated</strong>
</font>
</h2>
</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 15px;font-size: 1px;">&nbsp;</div>
</div>
<div style="Margin-left: 20px;Margin-right: 20px;">
<div style="mso-line-height-rule: exactly;line-height: 35px;font-size: 1px;">&nbsp;</div>
</div>
</div>
<!--[if (mso)|(IE)]></td><td class="layout__edges">&nbsp;</td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="mso-line-height-rule: exactly;line-height: 20px;font-size: 20px;">&nbsp;</div>
<div style="mso-line-height-rule: exactly;" role="contentinfo">
<div class="layout email-footer" style="Margin: 0 auto;max-width: 600px;min-width: 320px; width: 320px;width: calc(28000% - 167400px);overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;">
<div class="layout__inner" style="border-collapse: collapse;display: table;width: 100%;">
<!--[if (mso)|(IE)]><table align="center" cellpadding="0" cellspacing="0" role="presentation"><tr class="layout-email-footer"><td style="width: 400px;" valign="top" class="w360"><![endif]-->
<div class="column wide" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 400px;min-width: 320px; width: 320px;width: calc(8000% - 47600px);">
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
<div style="font-size: 12px;line-height: 19px;">
</div>
<div style="font-size: 12px;line-height: 19px;Margin-top: 18px;">
</div>
<!--[if mso]>&nbsp;<![endif]-->
</div>
</div>
<!--[if (mso)|(IE)]></td><td style="width: 200px;" valign="top" class="w160"><![endif]-->
<div class="column narrow" style="text-align: left;font-size: 12px;line-height: 19px;color: #adb3b9;font-family: sans-serif;Float: left;max-width: 320px;min-width: 200px; width: 320px;width: calc(72200px - 12000%);">
<div style="Margin-left: 20px;Margin-right: 20px;Margin-top: 10px;Margin-bottom: 10px;">
</div>
</div>
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
</div>
</div>
</div>
<div style="mso-line-height-rule: exactly;line-height: 40px;font-size: 40px;">&nbsp;</div>
</body>
</div>
</html>
`

34
notify/manager.go Normal file
View File

@@ -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 = &notifyManager{
notifications: initNotifications(conf),
}
})
return instance
}
func initNotifications(conf *godns.Settings) map[string]*INotify {
return map[string]*INotify{}
}

78
notify/slack.go Normal file
View File

@@ -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
}

75
notify/telegram.go Normal file
View File

@@ -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
}