feat: add notifier (#144)

* ♻️ refactor: email refactor

*  feat: add notifier
This commit is contained in:
Buer
2024-04-09 15:00:06 +08:00
committed by GitHub
parent 76d22f0572
commit a3719cd78a
33 changed files with 1386 additions and 188 deletions

168
common/stmp/email.go Normal file
View File

@@ -0,0 +1,168 @@
package stmp
import (
"fmt"
"one-api/common"
"strings"
"github.com/wneessen/go-mail"
)
type StmpConfig struct {
Host string
Port int
Username string
Password string
From string
}
func NewStmp(host string, port int, username string, password string, from string) *StmpConfig {
if from == "" {
from = username
}
return &StmpConfig{
Host: host,
Port: port,
Username: username,
Password: password,
From: from,
}
}
func (s *StmpConfig) Send(to, subject, body string) error {
message := mail.NewMsg()
message.From(s.From)
message.To(to)
message.Subject(subject)
message.SetGenHeader("References", s.getReferences())
message.SetBodyString(mail.TypeTextHTML, body)
message.SetUserAgent(fmt.Sprintf("One API %s // https://github.com/MartialBE/one-api", common.Version))
client, err := mail.NewClient(
s.Host,
mail.WithPort(s.Port),
mail.WithUsername(s.Username),
mail.WithPassword(s.Password),
mail.WithSMTPAuth(mail.SMTPAuthPlain))
if err != nil {
return err
}
switch s.Port {
case 465:
client.SetSSL(true)
case 587:
client.SetTLSPolicy(mail.TLSMandatory)
}
if err := client.DialAndSend(message); err != nil {
return err
}
return nil
}
func (s *StmpConfig) getReferences() string {
froms := strings.Split(s.From, "@")
return fmt.Sprintf("<%s.%s@%s>", froms[0], common.GetUUID(), froms[1])
}
func (s *StmpConfig) Render(to, subject, content string) error {
body := getDefaultTemplate(content)
return s.Send(to, subject, body)
}
func GetSystemStmp() (*StmpConfig, error) {
if common.SMTPServer == "" || common.SMTPPort == 0 || common.SMTPAccount == "" || common.SMTPToken == "" {
return nil, fmt.Errorf("SMTP 信息未配置")
}
return NewStmp(common.SMTPServer, common.SMTPPort, common.SMTPAccount, common.SMTPToken, common.SMTPFrom), nil
}
func SendPasswordResetEmail(userName, email, link string) error {
stmp, err := GetSystemStmp()
if err != nil {
return err
}
contentTemp := `<p style="font-size: 30px">Hi <strong>%s,</strong></p>
<p>
您正在进行密码重置。点击下方按钮以重置密码。
</p>
<p style="text-align: center; font-size: 13px;">
<a target="__blank" href="%s" class="button" style="color: #ffffff;">重置密码</a>
</p>
<p style="color: #858585; padding-top: 15px;">
如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开<br> %s
</p>
<p style="color: #858585;">重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>`
subject := fmt.Sprintf("%s密码重置", common.SystemName)
content := fmt.Sprintf(contentTemp, userName, link, link, common.VerificationValidMinutes)
return stmp.Render(email, subject, content)
}
func SendVerificationCodeEmail(email, code string) error {
stmp, err := GetSystemStmp()
if err != nil {
return err
}
contentTemp := `
<p>
您正在进行邮箱验证。您的验证码为:
</p>
<p style="text-align: center; font-size: 30px; color: #58a6ff;">
<strong>%s</strong>
</p>
<p style="color: #858585; padding-top: 15px;">
验证码 %d 分钟内有效,如果不是本人操作,请忽略。
</p>`
subject := fmt.Sprintf("%s邮箱验证邮件", common.SystemName)
content := fmt.Sprintf(contentTemp, code, common.VerificationValidMinutes)
return stmp.Render(email, subject, content)
}
func SendQuotaWarningCodeEmail(userName, email string, quota int, noMoreQuota bool) error {
stmp, err := GetSystemStmp()
if err != nil {
return err
}
contentTemp := `<p style="font-size: 30px">Hi <strong>%s,</strong></p>
<p>
%s当前剩余额度为 %d为了不影响您的使用请及时充值。
</p>
<p style="text-align: center; font-size: 13px;">
<a target="__blank" href="%s" class="button" style="color: #ffffff;">点击充值</a>
</p>
<p style="color: #858585; padding-top: 15px;">
如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开<br> %s
</p>`
subject := "您的额度即将用尽"
if noMoreQuota {
subject = "您的额度已用尽"
}
topUpLink := fmt.Sprintf("%s/topup", common.ServerAddress)
content := fmt.Sprintf(contentTemp, userName, subject, quota, topUpLink, topUpLink)
return stmp.Render(email, subject, content)
}

110
common/stmp/template.go Normal file
View File

@@ -0,0 +1,110 @@
package stmp
import (
"one-api/common"
)
func getLogo() string {
if common.Logo == "" {
return ""
}
return `<table class="logo" width="100%">
<tr>
<td>
<img src="` + common.Logo + `" width="130" style="max-width: 100%"
/>
</td>
</tr>
</table>`
}
func getSystemName() string {
if common.SystemName == "" {
return "One API"
}
return common.SystemName
}
func getDefaultTemplate(content string) string {
return `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style type="text/css">
body {
margin: 0;
background-color: #eceff1;
font-family: Helvetica, sans-serif;
}
table {
border-spacing: 0;
}
td {
padding: 0;
}
img {
border: 0;
}
.wrapper {
width: 100%;
table-layout: fixed;
background-color: #eceff1;
padding-bottom: 60px;
padding-top: 60px;
}
.main {
background-color: #ffffff;
border-spacing: 0;
color: #000000;
border-radius: 10px;
border-color: #ebebeb;
border-width: 1px;
border-style: solid;
padding: 10px 30px;
line-height: 25px;
font-size: 16px;
text-align: start;
width: 600px;
}
.button {
background-color: #000000;
color: #ffffff;
text-decoration: none;
padding: 12px 20px;
font-weight: bold;
border-radius: 5px;
}
.logo {
text-align: center;
margin: 10px auto;
}
.footer {
text-align: center;
color: #858585
}
</style>
</head>
<body>
<center class="wrapper">
` + getLogo() + `
<table class="main" width="100%">
<tr>
<td>
` + content + `
</td>
</tr>
</table>
<table class="footer" width="100%">
<tr>
<td width="100%">
<p>© ` + getSystemName() + `</p>
</td>
</tr>
</table>
</center>
</body>
</html>`
}