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:
55
notify/common.go
Normal file
55
notify/common.go
Normal 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
143
notify/email.go
Normal 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"> </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;"> </div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><td class="layout__edges"> </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"> </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;"> </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;"> </div>
|
||||
</div>
|
||||
|
||||
<div style="Margin-left: 20px;Margin-right: 20px;">
|
||||
<div style="mso-line-height-rule: exactly;line-height: 35px;font-size: 1px;"> </div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><td class="layout__edges"> </td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="mso-line-height-rule: exactly;line-height: 20px;font-size: 20px;"> </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]> <![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;"> </div>
|
||||
</body>
|
||||
</div>
|
||||
</html>
|
||||
`
|
||||
34
notify/manager.go
Normal file
34
notify/manager.go
Normal 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 = ¬ifyManager{
|
||||
notifications: initNotifications(conf),
|
||||
}
|
||||
})
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
func initNotifications(conf *godns.Settings) map[string]*INotify {
|
||||
return map[string]*INotify{}
|
||||
}
|
||||
78
notify/slack.go
Normal file
78
notify/slack.go
Normal 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
75
notify/telegram.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user