diff --git a/config.example.yaml b/config.example.yaml
index fcb9709..5dff392 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -15,6 +15,14 @@ mysql:
maxIdleConn: 10
# 连接的生命时长
connMaxLifetime: 236
+Mail:
+ user: xx@163.com
+ alias: xx
+ pass:
+ Host: smtp.163.com
+ Port: 465
+ Ssl: true
+
# 最近文章缓存时间
recentPostCacheTime: 5m
# 分类缓存时间
diff --git a/go.mod b/go.mod
index 211730a..563dcb3 100644
--- a/go.mod
+++ b/go.mod
@@ -34,4 +34,6 @@ require (
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
)
diff --git a/mail/mail.go b/mail/mail.go
new file mode 100644
index 0000000..80dd781
--- /dev/null
+++ b/mail/mail.go
@@ -0,0 +1,56 @@
+package mail
+
+import (
+ "crypto/tls"
+ "fmt"
+ "github/fthvgb1/wp-go/vars"
+ "gopkg.in/gomail.v2"
+ "mime"
+ "strings"
+)
+
+type AttacheFile struct {
+ Name string
+ Path string
+}
+
+func (f AttacheFile) GetName() string {
+ t := strings.Split(f.Path, ".")
+ return fmt.Sprintf("%s.%s", f.Name, t[len(t)-1])
+}
+
+func SendMail(mailTo []string, subject string, body string, a ...AttacheFile) error {
+ m := gomail.NewMessage(
+ gomail.SetEncoding(gomail.Base64),
+ )
+ m.SetHeader("From",
+ m.FormatAddress(vars.Conf.Mail.User,
+ vars.Conf.Mail.Alias,
+ ))
+ m.SetHeader("To", mailTo...)
+ m.SetHeader("Subject", subject)
+ m.SetBody("text/html", body)
+
+ for _, files := range a {
+ m.Attach(files.Path,
+ gomail.Rename(files.Name), //重命名
+ gomail.SetHeader(map[string][]string{
+ "Content-Disposition": {
+ fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", files.GetName())),
+ },
+ }),
+ )
+ }
+
+ d := gomail.NewDialer(
+ vars.Conf.Mail.Host,
+ vars.Conf.Mail.Port,
+ vars.Conf.Mail.User,
+ vars.Conf.Mail.Pass,
+ )
+ if vars.Conf.Mail.Ssl {
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+ }
+ err := d.DialAndSend(m)
+ return err
+}
diff --git a/mail/mail_test.go b/mail/mail_test.go
new file mode 100644
index 0000000..9d3fa01
--- /dev/null
+++ b/mail/mail_test.go
@@ -0,0 +1,43 @@
+package mail
+
+import (
+ "github/fthvgb1/wp-go/vars"
+ "testing"
+)
+
+func TestSendMail(t *testing.T) {
+ vars.InitConfig()
+ type args struct {
+ mailTo []string
+ subject string
+ body string
+ a []AttacheFile
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ }{
+ {
+ name: "t1",
+ args: args{
+ mailTo: []string{"fthvgb1@163.com"},
+ subject: "测试发邮件",
+ body: "测试发邮件",
+ a: []AttacheFile{
+ {
+ Name: "附件",
+ Path: "/home/xing/Downloads/favicon.ico",
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := SendMail(tt.args.mailTo, tt.args.subject, tt.args.body, tt.args.a...); (err != nil) != tt.wantErr {
+ t.Errorf("SendMail() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/middleware/searchlimit.go b/middleware/searchlimit.go
index ca17c7e..52a52cd 100644
--- a/middleware/searchlimit.go
+++ b/middleware/searchlimit.go
@@ -5,7 +5,7 @@ import "github.com/gin-gonic/gin"
func SearchLimit(num int64) func(ctx *gin.Context) {
fn := IpLimit(num)
return func(c *gin.Context) {
- if "/" == c.FullPath() && c.Query("s") != "" {
+ if c.Query("s") != "" {
fn(c)
} else {
c.Next()
diff --git a/middleware/sendmail.go b/middleware/sendmail.go
new file mode 100644
index 0000000..f9e1f2e
--- /dev/null
+++ b/middleware/sendmail.go
@@ -0,0 +1,139 @@
+package middleware
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "github/fthvgb1/wp-go/logs"
+ "github/fthvgb1/wp-go/mail"
+ "github/fthvgb1/wp-go/models"
+ "github/fthvgb1/wp-go/vars"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httputil"
+ "runtime"
+ "strings"
+ "time"
+)
+
+func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) {
+ return gin.CustomRecoveryWithWriter(w, func(ctx *gin.Context, err any) {
+ c := ctx.Copy()
+ stack := stack(4)
+ go func() {
+ httpRequest, _ := httputil.DumpRequest(c.Request, true)
+ headers := strings.Split(string(httpRequest), "\r\n")
+ for idx, header := range headers {
+ current := strings.Split(header, ":")
+ if current[0] == "Authorization" {
+ headers[idx] = current[0] + ": *"
+ }
+ }
+ headersToStr := strings.Join(headers, "
")
+ content := `
err:%v
+stack: %v
+headers: %s
+`
+ content = fmt.Sprintf(content,
+ err,
+ formatStack(string(stack)),
+ headersToStr,
+ )
+
+ er := mail.SendMail(
+ []string{vars.Conf.Mail.User},
+ fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(models.Options["siteurl"]), c.FullPath(), time.Now().Format(time.RFC1123Z)), content)
+
+ if er != nil {
+ logs.ErrPrintln(er, "recover send mail fail", fmt.Sprintf("%v", err))
+ }
+ }()
+ ctx.AbortWithStatus(http.StatusInternalServerError)
+ })
+}
+
+var (
+ dunno = []byte("???")
+ centerDot = []byte("·")
+ dot = []byte(".")
+ slash = []byte("/")
+)
+
+func formatStack(s string) (r string) {
+ ss := strings.Builder{}
+ t := strings.Split(s, "\n")
+ for i, line := range t {
+ if i%2 == 0 {
+ ss.WriteString("")
+ ss.WriteString(line)
+ ss.WriteString("")
+ } else {
+ ss.WriteString("")
+ ss.WriteString(strings.Trim(line, "\t"))
+ ss.WriteString("")
+ }
+ }
+ r = ss.String()
+ return
+}
+
+func stack(skip int) []byte {
+ buf := new(bytes.Buffer) // the returned data
+ // As we loop, we open files and read them. These variables record the currently
+ // loaded file.
+ var lines [][]byte
+ var lastFile string
+ for i := skip; ; i++ { // Skip the expected number of frames
+ pc, file, line, ok := runtime.Caller(i)
+ if !ok {
+ break
+ }
+ // Print this much at least. If we can't find the source, it won't show.
+ fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
+ if file != lastFile {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ continue
+ }
+ lines = bytes.Split(data, []byte{'\n'})
+ lastFile = file
+ }
+ fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
+ }
+ return buf.Bytes()
+}
+
+// function returns, if possible, the name of the function containing the PC.
+func function(pc uintptr) []byte {
+ fn := runtime.FuncForPC(pc)
+ if fn == nil {
+ return dunno
+ }
+ name := []byte(fn.Name())
+ // The name includes the path name to the package, which is unnecessary
+ // since the file name is already included. Plus, it has center dots.
+ // That is, we see
+ // runtime/debug.*T·ptrmethod
+ // and want
+ // *T.ptrmethod
+ // Also the package path might contain dot (e.g. code.google.com/...),
+ // so first eliminate the path prefix
+ if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
+ name = name[lastSlash+1:]
+ }
+ if period := bytes.Index(name, dot); period >= 0 {
+ name = name[period+1:]
+ }
+ name = bytes.Replace(name, centerDot, dot, -1)
+ return name
+}
+
+// source returns a space-trimmed slice of the n'th line.
+func source(lines [][]byte, n int) []byte {
+ n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
+ if n < 0 || n >= len(lines) {
+ return dunno
+ }
+ return bytes.TrimSpace(lines[n])
+}
diff --git a/route/route.go b/route/route.go
index 51340a5..38626f7 100644
--- a/route/route.go
+++ b/route/route.go
@@ -39,7 +39,7 @@ func SetupRouter() *gin.Engine {
r.Use(
middleware.ValidateServerNames(),
gin.Logger(),
- gin.Recovery(),
+ middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
middleware.FlowLimit(vars.Conf.MaxRequestSleepNum, vars.Conf.MaxRequestNum, vars.Conf.SleepTime),
middleware.SetStaticFileCache,
)
diff --git a/static/wp-includes/css/googleapicss.css b/static/wp-includes/css/googleapicss.css
new file mode 100644
index 0000000..844efb7
--- /dev/null
+++ b/static/wp-includes/css/googleapicss.css
@@ -0,0 +1,600 @@
+/* vietnamese */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 400;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyxq15Mjs.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 400;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyx615Mjs.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 400;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyya15.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 700;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyxq15Mjs.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 700;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyx615Mjs.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inconsolata';
+ font-style: normal;
+ font-weight: 700;
+ font-stretch: 100%;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/inconsolata/v31/QlddNThLqRwH-OJ1UHjlKENVzkWGVkL3GZQmAwLyya15.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARPQ_m87A.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARGQ_m87A.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* devanagari */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARDQ_m87A.woff2) format('woff2');
+ unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4AROQ_m87A.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARBQ_m87A.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARNQ_m87A.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARMQ_m87A.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0OIpQlx3QUlC5A4PNr4ARCQ_k.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyOzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyHzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* devanagari */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyCzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyPzW1aPQ.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyAzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyMzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyNzW1aPQ.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0TIpQlx3QUlC5A4PNr4Az5ZuyDzW0.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr6DRAW_0.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr4TRAW_0.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* devanagari */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr5DRAW_0.woff2) format('woff2');
+ unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr6TRAW_0.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr5jRAW_0.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr6jRAW_0.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr6zRAW_0.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNr5TRA.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVadyB1Wk.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVYNyB1Wk.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* devanagari */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVZdyB1Wk.woff2) format('woff2');
+ unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVaNyB1Wk.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVZ9yB1Wk.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVa9yB1Wk.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVatyB1Wk.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Sans';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notosans/v27/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImZzC7TMQ.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImbjC7TMQ.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImZjC7TMQ.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImaTC7TMQ.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImZTC7TMQ.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImZDC7TMQ.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Kaw1J5X9T9RW6j9bNfFImajC7.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufuVMCoY.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWud-VMCoY.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWuf-VMCoY.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWucOVMCoY.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufOVMCoY.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWufeVMCoY.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: italic;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Vaw1J5X9T9RW6j9bNfFIu0RWuc-VM.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFoWaCi_.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFMWaCi_.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFsWaCi_.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFQWaCi_.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFgWaCi_.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFkWaCi_.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 400;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Iaw1J5X9T9RW6j9bNfFcWaA.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfRqecf1I.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfROecf1I.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfRuecf1I.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfRSecf1I.woff2) format('woff2');
+ unicode-range: U+0370-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfRiecf1I.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfRmecf1I.woff2) format('woff2');
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Noto Serif';
+ font-style: normal;
+ font-weight: 700;
+ font-display: fallback;
+ src: url(https://fonts.gstatic.com/s/notoserif/v21/ga6Law1J5X9T9RW6j9bNdOwzfReecQ.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
\ No newline at end of file
diff --git a/vars/config.go b/vars/config.go
index f9e7050..123d894 100644
--- a/vars/config.go
+++ b/vars/config.go
@@ -11,6 +11,7 @@ var Conf Config
type Config struct {
Mysql Mysql `yaml:"mysql"`
+ Mail Mail `yaml:"mail"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime"`
@@ -37,6 +38,15 @@ type Config struct {
TrustServerNames []string `yaml:"trustServerNames"`
}
+type Mail struct {
+ User string `yaml:"user"`
+ Alias string `yaml:"alias"`
+ Pass string `yaml:"pass"`
+ Host string `yaml:"host"`
+ Port int `yaml:"port"`
+ Ssl bool `yaml:"ssl"`
+}
+
type Mysql struct {
Dsn Dsn `yaml:"dsn"`
Pool Pool `yaml:"pool"`