diff --git a/Dockerfile b/Dockerfile
index 5bd94ef..5d8e95a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,7 +14,6 @@ COPY --from=febuild /work/dist /work/server/http_server/dist
RUN apk update && apk add git
RUN cd /work/server && go build -ldflags "-s -w -X 'main.version=${VERSION}' -X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail main.go
RUN cd /work/server/hooks/telegram_push && go build -ldflags "-s -w" -o output/telegram_push telegram_push.go
-RUN cd /work/server/hooks/web_push && go build -ldflags "-s -w" -o output/web_push web_push.go
RUN cd /work/server/hooks/wechat_push && go build -ldflags "-s -w" -o output/wechat_push wechat_push.go
RUN cd /work/server/hooks/spam_block && go build -ldflags "-s -w" -o output/spam_block spam_block.go
@@ -32,7 +31,6 @@ RUN apk add --no-cache tzdata \
COPY --from=serverbuild /work/server/pmail .
COPY --from=serverbuild /work/server/hooks/telegram_push/output/* ./plugins/
-COPY --from=serverbuild /work/server/hooks/web_push/output/* ./plugins/
COPY --from=serverbuild /work/server/hooks/wechat_push/output/* ./plugins/
COPY --from=serverbuild /work/server/hooks/spam_block/output/* ./plugins/
diff --git a/DockerfileGithubAction b/DockerfileGithubAction
index 6c47197..0c29ec9 100644
--- a/DockerfileGithubAction
+++ b/DockerfileGithubAction
@@ -8,7 +8,6 @@ COPY server .
RUN apk update && apk add git
RUN go build -ldflags "-s -w -X 'main.version=${VERSION}' -X 'main.goVersion=$(go version)' -X 'main.gitHash=${GITHASH}' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail main.go
RUN cd /work/hooks/telegram_push && go build -ldflags "-s -w" -o output/telegram_push telegram_push.go
-RUN cd /work/hooks/web_push && go build -ldflags "-s -w" -o output/web_push web_push.go
RUN cd /work/hooks/wechat_push && go build -ldflags "-s -w" -o output/wechat_push wechat_push.go
RUN cd /work/hooks/spam_block && go build -ldflags "-s -w" -o output/spam_block spam_block.go
@@ -26,7 +25,6 @@ RUN apk add --no-cache tzdata \
COPY --from=serverbuild /work/pmail .
COPY --from=serverbuild /work/hooks/telegram_push/output/* ./plugins/
-COPY --from=serverbuild /work/hooks/web_push/output/* ./plugins/
COPY --from=serverbuild /work/hooks/wechat_push/output/* ./plugins/
COPY --from=serverbuild /work/hooks/spam_block/output/* ./plugins/
diff --git a/Makefile b/Makefile
index 90f6b8e..68cc118 100644
--- a/Makefile
+++ b/Makefile
@@ -21,11 +21,6 @@ telegram_push:
cd server/hooks/telegram_push && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o output/telegram_push_mac_amd64 telegram_push.go
cd server/hooks/telegram_push && CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o output/telegram_push_mac_arm64 telegram_push.go
-web_push:
- cd server/hooks/web_push && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o output/web_push_linux_amd64 web_push.go
- cd server/hooks/web_push && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o output/web_push_windows_amd64.exe web_push.go
- cd server/hooks/web_push && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o output/web_push_mac_amd64 web_push.go
- cd server/hooks/web_push && CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o output/web_push_mac_arm64 web_push.go
wechat_push:
cd server/hooks/wechat_push && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o output/wechat_push_linux_amd64 wechat_push.go
@@ -41,7 +36,7 @@ spam_block:
-plugin: telegram_push wechat_push web_push
+plugin: telegram_push wechat_push
package: clean
@@ -53,7 +48,6 @@ package: clean
cp -r server/config/ssl output/config/
cp -r server/config/config.json output/config/
mv server/hooks/telegram_push/output/* output/plugins
- mv server/hooks/web_push/output/* output/plugins
mv server/hooks/wechat_push/output/* output/plugins
cp README.md output/
diff --git a/fe/src/components/HomeHeader.vue b/fe/src/components/HomeHeader.vue
index e46c122..7f7db6f 100644
--- a/fe/src/components/HomeHeader.vue
+++ b/fe/src/components/HomeHeader.vue
@@ -25,6 +25,11 @@
+
+
+
+
+
@@ -41,6 +46,7 @@ import GroupSettings from './GroupSettings.vue';
import RuleSettings from './RuleSettings.vue';
import UserManagement from './UserManagement.vue';
import { getCurrentInstance } from 'vue'
+import PluginSettings from './PluginSettings.vue';
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const $isLogin = app.appContext.config.globalProperties.$isLogin
diff --git a/fe/src/components/PluginSettings.vue b/fe/src/components/PluginSettings.vue
new file mode 100644
index 0000000..0249c40
--- /dev/null
+++ b/fe/src/components/PluginSettings.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fe/src/i18n/i18n.js b/fe/src/i18n/i18n.js
index 1a28bc8..2f63a6a 100644
--- a/fe/src/i18n/i18n.js
+++ b/fe/src/i18n/i18n.js
@@ -7,6 +7,7 @@ var lang = {
"editUser": "Edit Account",
"user_name": "User Name",
"user_management": "user management",
+ "plugin_settings": "Plugin Settings",
"lang": "en",
"submit": "submit",
"compose": "compose",
@@ -117,6 +118,7 @@ var zhCN = {
"newUser": "新增用户",
"editUser": "编辑用户",
"user_management": "用户管理",
+ "plugin_settings": "插件设置",
"lang": "zhCn",
"submit": "提交",
"compose": "发件",
diff --git a/server/config/config.json b/server/config/config.json
index 36ad3aa..7186b54 100644
--- a/server/config/config.json
+++ b/server/config/config.json
@@ -9,7 +9,7 @@
"SSLPublicKeyPath": "./config/ssl/public.crt",
"dbDSN": "./config/pmail_temp.db",
"dbType": "sqlite",
- "httpsEnabled": 1,
+ "httpsEnabled": 2,
"spamFilterLevel": 0,
"httpPort": 80,
"httpsPort": 443,
diff --git a/server/controllers/plugin.go b/server/controllers/plugin.go
new file mode 100644
index 0000000..9d4e7d3
--- /dev/null
+++ b/server/controllers/plugin.go
@@ -0,0 +1,46 @@
+package controllers
+
+import (
+ "github.com/Jinnrry/pmail/dto/response"
+ "github.com/Jinnrry/pmail/hooks"
+ "github.com/Jinnrry/pmail/utils/context"
+ "io"
+ "net/http"
+ "strings"
+)
+
+func GetPluginList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
+ ret := []string{}
+ for s, _ := range hooks.HookList {
+ ret = append(ret, s)
+ }
+ response.NewSuccessResponse(ret).FPrint(w)
+
+}
+
+func SettingsHtml(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
+ args := strings.Split(req.RequestURI, "/")
+ if len(args) < 4 {
+ response.NewErrorResponse(response.ParamsError, "404", "").FPrint(w)
+ return
+ }
+
+ pluginName := args[4]
+ if plugin, ok := hooks.HookList[pluginName]; ok {
+ dt, err := io.ReadAll(req.Body)
+ if err != nil {
+ response.NewErrorResponse(response.ParamsError, err.Error(), "").FPrint(w)
+ return
+ }
+ html := plugin.SettingsHtml(ctx,
+ strings.Join(args[4:], "/"),
+ string(dt),
+ )
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+ w.Write([]byte(html))
+ return
+
+ }
+ response.NewErrorResponse(response.ParamsError, "404", "")
+}
diff --git a/server/dto/parsemail/email.go b/server/dto/parsemail/email.go
index a4e7c4c..b57e574 100644
--- a/server/dto/parsemail/email.go
+++ b/server/dto/parsemail/email.go
@@ -54,15 +54,17 @@ type Email struct {
Date string
Status int // 0未发送,1已发送,2发送失败,3删除
MessageId int64
+ Size int
}
-func NewEmailFromReader(to []string, r io.Reader) *Email {
+func NewEmailFromReader(to []string, r io.Reader, size int) *Email {
ret := &Email{}
m, err := message.Read(r)
if err != nil {
log.Errorf("email解析错误! Error %+v", err)
}
+ ret.Size = size
ret.From = buildUser(m.Header.Get("From"))
if len(to) > 0 {
diff --git a/server/hooks/base.go b/server/hooks/base.go
index e9e38ae..5ee7455 100644
--- a/server/hooks/base.go
+++ b/server/hooks/base.go
@@ -137,6 +137,47 @@ func (h *HookSender) ReceiveParseAfter(ctx *context.Context, email *parsemail.Em
}
+// GetName 获取插件名称
+func (h *HookSender) GetName(ctx *context.Context) string {
+
+ dto := framework.HookDTO{
+ Ctx: ctx,
+ }
+ body, _ := json.Marshal(dto)
+
+ ret, errL := h.httpc.Post("http://plugin/GetName", "application/json", strings.NewReader(string(body)))
+ if errL != nil {
+ log.WithContext(ctx).Errorf("[%s] Error! %v", h.name, errL)
+ return ""
+ }
+
+ body, _ = io.ReadAll(ret.Body)
+
+ return string(body)
+}
+
+// SettingsHtml 插件页面
+func (h *HookSender) SettingsHtml(ctx *context.Context, url string, requestData string) string {
+
+ dto := framework.SettingsHtmlRequest{
+ Ctx: ctx,
+ URL: url,
+ RequestData: requestData,
+ }
+ body, _ := json.Marshal(dto)
+
+ ret, errL := h.httpc.Post("http://plugin/SettingsHtml", "application/json", strings.NewReader(string(body)))
+ if errL != nil {
+ log.WithContext(ctx).Errorf("[%s] Error! %v", h.name, errL)
+ return ""
+ }
+
+ body, _ = io.ReadAll(ret.Body)
+
+ return string(body)
+
+}
+
func NewHookSender(socketPath string, name string, serverVersion string) *HookSender {
httpc := http.Client{
Timeout: time.Second * 10,
@@ -214,8 +255,10 @@ func Init(serverVersion string) {
}
}
if loadSucc {
- HookList[info.Name()] = NewHookSender(socketPath, info.Name(), serverVersion)
- log.Infof("[%s] Plugin Load Success!", info.Name())
+ hk := NewHookSender(socketPath, info.Name(), serverVersion)
+ hkName := hk.GetName(&context.Context{})
+ HookList[hkName] = hk
+ log.Infof("[%s] Plugin Load Success!", hkName)
}
}
diff --git a/server/hooks/framework/framework.go b/server/hooks/framework/framework.go
index 7a09495..c74c563 100644
--- a/server/hooks/framework/framework.go
+++ b/server/hooks/framework/framework.go
@@ -13,6 +13,7 @@ import (
"net/http"
"os"
"path/filepath"
+ "strings"
"time"
)
@@ -27,6 +28,10 @@ type EmailHook interface {
ReceiveParseAfter(ctx *context.Context, email *parsemail.Email)
// ReceiveSaveAfter 邮件落库以后执行(收信规则后执行) 异步执行
ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail)
+ // GetName 获取插件名称
+ GetName(ctx *context.Context) string
+ // SettingsHtml 插件页面
+ SettingsHtml(ctx *context.Context, url string, requestData string) string
}
// HookDTO PMail 主程序和插件通信的结构体
@@ -39,6 +44,12 @@ type HookDTO struct {
UserEmail []*models.UserEmail
}
+type SettingsHtmlRequest struct {
+ Ctx *context.Context // 上下文
+ URL string
+ RequestData string
+}
+
type Plugin struct {
name string
hook EmailHook
@@ -160,6 +171,32 @@ func (p *Plugin) Run() {
writer.Write(body)
log.Debugf("[%s] ReceiveSaveAfter End", p.name)
})
+ mux.HandleFunc("/GetName", func(writer http.ResponseWriter, request *http.Request) {
+ log.Debugf("[%s] GetName Start", p.name)
+ var hookDTO HookDTO
+ body, _ := io.ReadAll(request.Body)
+ err := json.Unmarshal(body, &hookDTO)
+ if err != nil {
+ log.Errorf("params error %+v", err)
+ return
+ }
+ name := strings.ReplaceAll(p.hook.GetName(hookDTO.Ctx), " ", "")
+ writer.Write([]byte(name))
+ log.Debugf("[%s] GetName End", p.name)
+ })
+ mux.HandleFunc("/SettingsHtml", func(writer http.ResponseWriter, request *http.Request) {
+ log.Debugf("[%s] SettingsHtml Start", p.name)
+ var hookDTO SettingsHtmlRequest
+ body, _ := io.ReadAll(request.Body)
+ err := json.Unmarshal(body, &hookDTO)
+ if err != nil {
+ log.Errorf("params error %+v", err)
+ return
+ }
+ html := p.hook.SettingsHtml(hookDTO.Ctx, hookDTO.URL, hookDTO.RequestData)
+ writer.Write([]byte(html))
+ log.Debugf("[%s] SettingsHtml End", p.name)
+ })
server := http.Server{
ReadTimeout: 5 * time.Second,
diff --git a/server/hooks/spam_block/README.md b/server/hooks/spam_block/README.md
index c476973..84fb708 100644
--- a/server/hooks/spam_block/README.md
+++ b/server/hooks/spam_block/README.md
@@ -52,16 +52,11 @@ curl -X POST http://localhost:8501/v1/models/emotion_model:predict -d '{
4、将spam_block插件移动到pmail插件目录
-5、在插件位置新建配置文件`spam_block_config.json`内容类似
+5、设置插件
-```json
-{
- "apiURL": "http://localhost:8501/v1/models/emotion_model:predict",
- "apiTimeout": 3000
-}
-```
+PMail后台->右上角设置按钮->插件设置->SpamBlock
-apiURL表示模型api访问地址,如果你是使用Docker部署,PMail和tensorflow/serving容器需要设置为相同网络才能通信,并且需要把localhost替换为tensorflow/serving的容器名称
+接口地址表示模型api访问地址,如果你是使用Docker部署,PMail和tensorflow/serving容器需要设置为相同网络才能通信,并且需要把localhost替换为tensorflow/serving的容器名称
# 模型效果
diff --git a/server/hooks/spam_block/Makefile b/server/hooks/spam_block/export/Makefile
similarity index 100%
rename from server/hooks/spam_block/Makefile
rename to server/hooks/spam_block/export/Makefile
diff --git a/server/hooks/spam_block/requirements.txt b/server/hooks/spam_block/requirements.txt
index 0922f27..ca799db 100644
--- a/server/hooks/spam_block/requirements.txt
+++ b/server/hooks/spam_block/requirements.txt
@@ -1,4 +1,6 @@
datasets==2.20.0
numpy==1.20.3
retvec==1.0.1
-tensorflow==2.8.4
+tensorflow==2.12.1
+beautifulsoup4
+lxml
\ No newline at end of file
diff --git a/server/hooks/spam_block/spam_block.go b/server/hooks/spam_block/spam_block.go
index fb8caf5..fca99b0 100644
--- a/server/hooks/spam_block/spam_block.go
+++ b/server/hooks/spam_block/spam_block.go
@@ -1,6 +1,7 @@
package main
import (
+ _ "embed"
"encoding/json"
"fmt"
"github.com/Jinnrry/pmail/dto/parsemail"
@@ -9,6 +10,7 @@ import (
"github.com/Jinnrry/pmail/models"
"github.com/Jinnrry/pmail/utils/context"
log "github.com/sirupsen/logrus"
+ "github.com/spf13/cast"
"io"
"net/http"
"os"
@@ -21,15 +23,64 @@ type SpamBlock struct {
hc *http.Client
}
-func (s SpamBlock) SendBefore(ctx *context.Context, email *parsemail.Email) {
+func (s *SpamBlock) SendBefore(ctx *context.Context, email *parsemail.Email) {
}
-func (s SpamBlock) SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) {
+func (s *SpamBlock) SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) {
}
-func (s SpamBlock) ReceiveParseBefore(ctx *context.Context, email *[]byte) {
+func (s *SpamBlock) ReceiveParseBefore(ctx *context.Context, email *[]byte) {
+
+}
+
+// GetName 获取插件名称
+func (s *SpamBlock) GetName(ctx *context.Context) string {
+ return "SpamBlock"
+}
+
+//go:embed static/index.html
+var index string
+
+//go:embed static/jquery.js
+var jquery string
+
+// SettingsHtml 插件页面
+func (s *SpamBlock) SettingsHtml(ctx *context.Context, url string, requestData string) string {
+
+ if strings.Contains(url, "jquery.js") {
+ return jquery
+ }
+
+ if strings.Contains(url, "index.html") {
+ if !ctx.IsAdmin {
+ return fmt.Sprintf(`
+
+ Please contact the administrator for configuration.
+
+`)
+ }
+ return fmt.Sprintf(index, s.cfg.ApiURL, s.cfg.ApiTimeout, s.cfg.Threshold)
+ }
+
+ var cfg SpamBlockConfig
+ var tempCfg map[string]string
+ err := json.Unmarshal([]byte(requestData), &tempCfg)
+ if err != nil {
+ return err.Error()
+ }
+ cfg.ApiURL = tempCfg["url"]
+ cfg.Threshold = cast.ToFloat64(tempCfg["threshold"])
+ cfg.ApiTimeout = cast.ToInt(tempCfg["timeout"])
+ err = saveConfig(cfg)
+ if err != nil {
+ return err.Error()
+ }
+
+ s.cfg = cfg
+
+ return "success"
}
@@ -45,7 +96,11 @@ type InstanceItem struct {
Token []string `json:"token"`
}
-func (s SpamBlock) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) {
+func (s *SpamBlock) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) {
+
+ if s.cfg.ApiURL == "" {
+ return
+ }
reqData := ApiRequest{
Instances: []InstanceItem{
@@ -94,25 +149,26 @@ func (s SpamBlock) ReceiveParseAfter(ctx *context.Context, email *parsemail.Emai
switch maxClass {
case 0:
- log.WithContext(ctx).Infof("[Spam Check Result: Normal] %s", email.Subject)
+ log.WithContext(ctx).Infof("[Spam Check Result: %f Normal] %s", maxScore, email.Subject)
case 1:
- log.WithContext(ctx).Infof("[Spam Check Result: Spam ] %s", email.Subject)
+ log.WithContext(ctx).Infof("[Spam Check Result: %f Spam ] %s", maxScore, email.Subject)
case 2:
- log.WithContext(ctx).Infof("[Spam Check Result: Blackmail ] %s", email.Subject)
+ log.WithContext(ctx).Infof("[Spam Check Result: %f Blackmail ] %s", maxScore, email.Subject)
}
- if maxClass != 0 {
+ if maxClass != 0 && maxScore > s.cfg.Threshold/100 {
email.Status = 3
}
}
-func (s SpamBlock) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
+func (s *SpamBlock) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
}
type SpamBlockConfig struct {
- ApiURL string `json:"apiURL"`
- ApiTimeout int `json:"apiTimeout"` // 单位毫秒
+ ApiURL string `json:"apiURL"`
+ ApiTimeout int `json:"apiTimeout"` // 单位毫秒
+ Threshold float64 `json:"threshold"`
}
func NewSpamBlockHook() *SpamBlock {
@@ -123,20 +179,18 @@ func NewSpamBlockHook() *SpamBlock {
if err == nil {
json.Unmarshal(cfgData, &pluginConfig)
}
- } else {
- log.Infof("No Config file found")
- return nil
}
log.Infof("Config: %+v", pluginConfig)
- if pluginConfig.ApiURL == "" {
- pluginConfig.ApiURL = "http://localhost:8501/v1/models/emotion_model:predict"
- }
if pluginConfig.ApiTimeout == 0 {
pluginConfig.ApiTimeout = 3000
}
+ if pluginConfig.Threshold == 0 {
+ pluginConfig.Threshold = 80
+ }
+
hc := &http.Client{
Timeout: time.Duration(pluginConfig.ApiTimeout) * time.Millisecond,
}
@@ -147,6 +201,12 @@ func NewSpamBlockHook() *SpamBlock {
}
}
+func saveConfig(cfg SpamBlockConfig) error {
+ data, _ := json.Marshal(cfg)
+ err := os.WriteFile("./plugins/spam_block_config.json", data, 0777)
+ return err
+}
+
func main() {
log.Infof("SpamBlockPlug Star Success")
instance := NewSpamBlockHook()
diff --git a/server/hooks/spam_block/static/index.html b/server/hooks/spam_block/static/index.html
new file mode 100644
index 0000000..e47b536
--- /dev/null
+++ b/server/hooks/spam_block/static/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/hooks/spam_block/static/jquery.js b/server/hooks/spam_block/static/jquery.js
new file mode 100644
index 0000000..798cc8b
--- /dev/null
+++ b/server/hooks/spam_block/static/jquery.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 096We’d love to hear your feedback about all of the changes. Please take a moment to complete our short survey.͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ JetBrains Research GoLand 2024.2 EAP: New Features and Improvements SurveyShare your feedback Greetings from JetBrains!We hope you've been enjoying the new features in the GoLand EAP builds. We’d love to hear your feedback about all of the changes. Please take a moment to complete our shortsurvey.By completing the survey and providing us with your contact information, you will be automatically entered into our prize draw. The winners will be picked at random, and each winner will receive one of threeone-year individual GoLand subscriptions.If you haven’t yet tried the latest EAP build, you can download ithere.Best regards,The JetBrains GoLand teamThe Drive to Develop Mail preferences | Unsubscribe | Privacy policyYou have received this email because you gave us permission to contact you for feedback about the GoLand EAP.Our mailing address: JetBrains s.r.o.,Na Hřebenech II 1718/8, 14000 Prague 4, Czech Republic
0 Re: [Jinnrry/PMail] 请问如何设置PMail和nginx reverse proxy? (Issue #151) 之前mx记录弄错了现在出现了timeoutyeeler@origin:~/PMail/server$ docker compose upWARN[0000] /home/yeeler/PMail/server/docker-compose.yml:versionis obsolete[+] Running 1/0✔ Container server-pmail-1Created0.0sAttaching to pmail-1pmail-1| [info][2024-07-10 21:26:42][/work/main.go:81]*******************************************************************pmail-1| [info][2024-07-10 21:26:42][/work/main.go:82]*** Server Start Successpmail-1|pmail-1| [info][2024-07-10 21:26:42][/work/main.go:83]*** Server Version: v2.5.3pmail-1|pmail-1| [info][2024-07-10 21:26:42][/work/main.go:84]*** Git Commit Hash:402e001pmail-1| [info][2024-07-10 21:26:42][/work/main.go:85]*** Build Date: 2024-07-08 16:49:03pmail-1| [info][2024-07-10 21:26:42][/work/main.go:86]*** Build GoLang Version: go version go1.22.5 linux/amd64pmail-1| [info][2024-07-10 21:26:42][/work/main.go:87]*******************************************************************pmail-1| [info][2024-07-10 21:26:42][/work/hooks/base.go:181][telegram_push] Plugin Loadpmail-1| [telegram_push] Plugin Start! PID:12[info][2024-07-10 21:26:43][/work/hooks/base.go:213][telegram_push] Plugin Load Success!pmail-1| [info][2024-07-10 21:26:43][/work/hooks/base.go:181][web_push] Plugin Loadpmail-1| [web_push] Plugin Start! PID:16[info][2024-07-10 21:26:44][/work/hooks/base.go:213][web_push] Plugin Load Success!pmail-1| [info][2024-07-10 21:26:44][/work/hooks/base.go:181][wechat_push] Plugin Loadpmail-1| [wechat_push] Plugin Start! PID:21[info][2024-07-10 21:26:45][/work/hooks/base.go:213][wechat_push] Plugin Load Success!pmail-1| [warning][2024-07-10 21:26:45][/work/res_init/init.go:52]Config File Info:{"logLevel":"debug","domain":"aaa.com","domains":["aaa.com"],"webDomain":"email.aaa.com","dkimPrivateKeyPath":"config/dkim/dkim.priv","sslType":"1","SSLPrivateKeyPath":"./config/ssl/private.key","SSLPublicKeyPath":"./config/ssl/public.crt","dbDSN":"./config/pmail_temp.db","dbType":"sqlite","httpsEnabled":2,"spamFilterLevel":0,"httpPort":80,"httpsPort":443,"weChatPushAppId":"","weChatPushSecret":"","weChatPushTemplateId":"","weChatPushUserId":"","tgBotToken":"","tgChatId":"","isInit":true,"webPushUrl":"","webPushToken":""}pmail-1| [info][2024-07-10 21:26:45][/work/pop3_server/pop3server.go:27]POP3 With TLS Server Start On Port :995pmail-1| [info][2024-07-10 21:26:45][/work/smtp_server/smtp.go:175]Starting Smtp Server Port: :25pmail-1| [info][2024-07-10 21:26:45][/work/http_server/http_server.go:80]HttpServer Start On Port :80pmail-1| [info][2024-07-10 21:26:45][/work/pop3_server/pop3server.go:46]POP3 Server Start On Port :110pmail-1| [info][2024-07-10 21:26:45][/work/smtp_server/smtp.go:147]Starting Smtp With SSL Server Port: :465pmail-1| [info][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/controllers/email/send.go:57]发送邮件pmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/controllers/email/send.go:152]插件执行--SendBeforepmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:50][telegram_push]Plugin SendBefore Startpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:69][telegram_push]Plugin SendBefore Endpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:50][web_push]Plugin SendBefore Startpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:69][web_push]Plugin SendBefore Endpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:50][wechat_push]Plugin SendBefore Startpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:69][wechat_push]Plugin SendBefore Endpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/controllers/email/send.go:159]插件执行--SendBefore Endpmail-1| [debug][2024-07-10 21:27:09][c0a84002668e8c2d62c700014870dcb0][/work/utils/send/send.go:126]Message Infos : DKIM-Signature: a=rsa-sha256; bh=LmOUdOOQ1/nljnKoO+A4LqHI/wp0btxn1QU+LJ6q/KM=;pmail-1|c=simple/simple; d=aaa.com;pmail-1|h=Content-Type:Mime-Version:Subject:To:From:Message-Id:Date; s=default;pmail-1|t=1720618029; v=1;pmail-1|b=ajOTutt37JmYyfXot/+WtkQFNwsIjyqgjvVBCy6BeeR4GIj16gSBJUFfOvWtC/urV1Ns1NKFpmail-1|gCkkRknwgLnwGNhszgtgSZfBJ/C+G6QbMpBT3HGpan9zCLP/9dK28Rku3t5QXPffR/l/DhcIP1Mpmail-1|wLZ16N5inu8V1rJe6PolC8H0=pmail-1| Content-Type: multipart/mixed;pmail-1|boundary=86904f13d11768291e792468b471ea7ca9eaaf80733a3dd90a9da112bb2apmail-1| Mime-Version: 1.0pmail-1| Subject: 44444444444pmail-1| To:yeeler@21cn.compmail-1| From: "admin"admin@aaa.compmail-1| Message-Id:10@aaa.compmail-1| Date: Wed, 10 Jul 2024 21:27:09 +0800pmail-1|pmail-1| --86904f13d11768291e792468b471ea7ca9eaaf80733a3dd90a9da112bb2apmail-1| Content-Type: multipart/alternative;pmail-1|boundary=b49b13e6359823c5cfe3ec38d9e0acc316219598eb38698056dc13a0e64cpmail-1|pmail-1| --b49b13e6359823c5cfe3ec38d9e0acc316219598eb38698056dc13a0e64cpmail-1| Content-Transfer-Encoding: quoted-printablepmail-1| Content-Disposition: inlinepmail-1| Content-Type: text/plain; charset=UTF-8pmail-1|pmail-1| hellopmail-1| --b49b13e6359823c5cfe3ec38d9e0acc316219598eb38698056dc13a0e64cpmail-1| Content-Transfer-Encoding: quoted-printablepmail-1| Content-Disposition: inlinepmail-1| Content-Type: text/html; charset=UTF-8pmail-1|pmail-1|hellopmail-1| --b49b13e6359823c5cfe3ec38d9e0acc316219598eb38698056dc13a0e64c--pmail-1|pmail-1| --86904f13d11768291e792468b471ea7ca9eaaf80733a3dd90a9da112bb2a--pmail-1|pmail-1| [error][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/utils/send/send.go:206][0xc0000e3ba0] 邮件投递失败dial tcp 74.125.130.27:25: i/o timeoutpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/controllers/email/send.go:196]插件执行--SendAfterpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:74][wechat_push]Plugin SendAfter Startpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:74][web_push]Plugin SendAfter Startpmail-1| [error][2024-07-10 21:27:11][/work/hooks/framework/framework.go:112]params error json: cannot unmarshal object into Go struct field HookDTO.ErrMap of type errorpmail-1| [error][2024-07-10 21:27:11][/work/hooks/framework/framework.go:112]params error json: cannot unmarshal object into Go struct field HookDTO.ErrMap of type errorpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:74][telegram_push]Plugin SendAfter Startpmail-1| [error][2024-07-10 21:27:11][/work/hooks/framework/framework.go:112]params error json: cannot unmarshal object into Go struct field HookDTO.ErrMap of type errorpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:88][web_push]Plugin SendAfter Endpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:88][wechat_push]Plugin SendAfter Endpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/hooks/base.go:88][telegram_push]Plugin SendAfter Endpmail-1| [debug][2024-07-10 21:27:11][c0a84002668e8c2d62c700014870dcb0][/work/controllers/email/send.go:208]插件执行--SendAfter—Reply to this email directly,view it on GitHub, orunsubscribe.You are receiving this because you commented.Message ID:<Jinnrry/PMail/issues/151/2220523641@github.com>
2 关于公司职员劳补福利津贴申领通知! 各位同事:具体详细请查阅附件!
diff --git a/server/hooks/spam_block/trec07p_format.py b/server/hooks/spam_block/trec07p_format.py
new file mode 100644
index 0000000..786f8bc
--- /dev/null
+++ b/server/hooks/spam_block/trec07p_format.py
@@ -0,0 +1,57 @@
+import os
+from email.parser import Parser
+from email.policy import default
+from bs4 import BeautifulSoup
+
+
+# 该脚本用于整理trec06c数据集,可以生成训练集和测试集数据格式
+
+def getData(path):
+ f = open(path, 'r', errors='ignore')
+ data = f.read()
+ headers = Parser(policy=default).parsestr(data)
+ body = ""
+ if headers.is_multipart():
+ for part in headers.iter_parts():
+ tbody = part.get_payload()
+ if isinstance(tbody, list):
+ for item in tbody:
+ txt = item.get_payload()
+ if isinstance(tbody, list):
+ return "", ""
+ bsObj = BeautifulSoup(txt, 'lxml')
+ body += bsObj.get_text()
+ else:
+ bsObj = BeautifulSoup(tbody, 'lxml')
+ body += bsObj.get_text()
+ else:
+ tbody = headers.get_payload()
+ bsObj = BeautifulSoup(tbody, 'lxml')
+ body += bsObj.get_text()
+ return headers["subject"], body.replace("\n", "")
+
+
+num = 0
+
+# getData("../data/000/000")
+with open("index", "r") as f:
+ with open("trec07p_train.csv", "w") as w:
+ with open("trec07p_test.csv", "w") as wt:
+ while True:
+ line = f.readline()
+ if not line:
+ break
+ infos = line.split(" ")
+ subject, body = getData(infos[1].strip())
+ if subject == "":
+ continue
+ tp = 0
+ if infos[0].lower() == "spam":
+ tp = 1
+ data = "{} \t{} {}\n".format(tp, subject, body)
+ if num < 55000:
+ w.write(data)
+ else:
+ wt.write(data)
+ num += 1
+print(num)
diff --git a/server/hooks/telegram_push/telegram_push.go b/server/hooks/telegram_push/telegram_push.go
index 683bded..3713037 100644
--- a/server/hooks/telegram_push/telegram_push.go
+++ b/server/hooks/telegram_push/telegram_push.go
@@ -35,6 +35,20 @@ func (w *TelegramPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsema
}
+// GetName 获取插件名称
+func (w *TelegramPushHook) GetName(ctx *context.Context) string {
+ return "TgPush"
+}
+
+// SettingsHtml 插件页面
+func (w *TelegramPushHook) SettingsHtml(ctx *context.Context, url string, requestData string) string {
+ return fmt.Sprintf(`
+
+ TG push No Settings Page
+
+`)
+}
+
func (w *TelegramPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
}
diff --git a/server/hooks/web_push/README.md b/server/hooks/web_push/README.md
deleted file mode 100644
index fe3f793..0000000
--- a/server/hooks/web_push/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-## How To Ues
-
-
-Copy plugin binary file to `/plugins`
-
-add config.json to `/plugins/config.com` like this:
-
-```json
-{
- "webPushUrl": "", // webhook push URL
- "webPushToken": "", // webhook push token
-}
-
-```
\ No newline at end of file
diff --git a/server/hooks/web_push/web_push.go b/server/hooks/web_push/web_push.go
deleted file mode 100644
index 8135f17..0000000
--- a/server/hooks/web_push/web_push.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "github.com/Jinnrry/pmail/config"
- "github.com/Jinnrry/pmail/dto/parsemail"
- "github.com/Jinnrry/pmail/hooks/framework"
- "github.com/Jinnrry/pmail/models"
- "github.com/Jinnrry/pmail/utils/context"
- log "github.com/sirupsen/logrus"
- "net/http"
- "os"
-)
-
-type WebPushHook struct {
- url string
- token string
-}
-
-func (w *WebPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
- if w.url == "" {
- return
- }
-
- content := string(email.Text)
-
- if content == "" {
- content = email.Subject
- }
-
- webhookURL := w.url // 替换为您的 Webhook URL
-
- to := make([]string, len(email.To))
- for i, user := range email.To {
- to[i] = user.EmailAddress
- }
-
- data := EmailData{
- From: email.From.EmailAddress,
- To: to,
- Subject: email.Subject,
- Body: content,
- Token: w.token,
- }
-
- jsonData, err := json.Marshal(data)
-
- if err != nil {
- log.WithContext(ctx).Errorf("web push error %+v", err)
- }
-
- resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
- if err != nil {
- log.WithContext(ctx).Errorf("web push error %+v", err)
- }
- defer resp.Body.Close()
-}
-
-// EmailData 用于存储解析后的邮件数据
-type EmailData struct {
- From string `json:"from"`
- To []string `json:"to"`
- Subject string `json:"subject"`
- Body string `json:"body"`
- Token string `json:"token"`
-}
-
-func (w *WebPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
-
-}
-
-func (w *WebPushHook) SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) {
-
-}
-
-func (w *WebPushHook) ReceiveParseBefore(ctx *context.Context, email *[]byte) {
-
-}
-
-func (w *WebPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) {
-}
-
-type Config struct {
- WebPushUrl string `json:"webPushUrl"`
- WebPushToken string `json:"webPushToken"`
-}
-
-func NewWebPushHook() *WebPushHook {
- var cfgData []byte
- var err error
-
- cfgData, err = os.ReadFile("./config/config.json")
- if err != nil {
- panic(err)
- }
- var mainConfig *config.Config
- err = json.Unmarshal(cfgData, &mainConfig)
- if err != nil {
- panic(err)
- }
-
- var pluginConfig *Config
- if _, err := os.Stat("./plugins/web_push_config.json"); err == nil {
- cfgData, err = os.ReadFile("./plugins/web_push_config.json")
- if err != nil {
- panic(err)
- }
- err = json.Unmarshal(cfgData, &pluginConfig)
- if err != nil {
- panic(err)
- }
-
- }
-
- token := ""
- pushURL := ""
- if pluginConfig != nil {
- pushURL = pluginConfig.WebPushUrl
- token = pluginConfig.WebPushToken
- } else {
- pushURL = mainConfig.WebPushUrl
- token = mainConfig.WebPushToken
- }
-
- ret := &WebPushHook{
- url: pushURL,
- token: token,
- }
- return ret
-
-}
-
-func main() {
- framework.CreatePlugin("web_push", NewWebPushHook()).Run()
-}
diff --git a/server/hooks/wechat_push/wechat_push.go b/server/hooks/wechat_push/wechat_push.go
index b3e4bd8..38541c5 100644
--- a/server/hooks/wechat_push/wechat_push.go
+++ b/server/hooks/wechat_push/wechat_push.go
@@ -32,6 +32,19 @@ type WeChatPushHook struct {
mainConfig *config.Config
}
+func (w *WeChatPushHook) GetName(ctx *context.Context) string {
+ return "WeChatPushHook"
+}
+
+// SettingsHtml 插件页面
+func (w *WeChatPushHook) SettingsHtml(ctx *context.Context, url string, requestData string) string {
+ return fmt.Sprintf(`
+
+ TG push No Settings Page
+
+`)
+}
+
func (w *WeChatPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
if w.appId == "" || w.secret == "" || w.pushUser == "" {
return
diff --git a/server/http_server/http_server.go b/server/http_server/http_server.go
index 06ced6d..83a9b2e 100644
--- a/server/http_server/http_server.go
+++ b/server/http_server/http_server.go
@@ -54,6 +54,8 @@ func router(mux *http.ServeMux) {
mux.HandleFunc("/api/user/edit", contextIterceptor(controllers.EditUser))
mux.HandleFunc("/api/user/info", contextIterceptor(controllers.Info))
mux.HandleFunc("/api/user/list", contextIterceptor(controllers.UserList))
+ mux.HandleFunc("/api/plugin/settings/", contextIterceptor(controllers.SettingsHtml))
+ mux.HandleFunc("/api/plugin/list", contextIterceptor(controllers.GetPluginList))
}
func HttpStart() {
diff --git a/server/pop3_server/action.go b/server/pop3_server/action.go
index 9b38013..9617cbc 100644
--- a/server/pop3_server/action.go
+++ b/server/pop3_server/action.go
@@ -185,15 +185,12 @@ func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, erro
var res []listItem
- var err error
- var ssql string
-
- err = db.Instance.Where("type=0 and status=0").Select("id").Table("email").Find(&res)
-
- if err != nil && !errors.Is(err, sql.ErrNoRows) {
- log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
- err = nil
- return []gopop.UidlItem{}, nil
+ emailList, _ := list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
+ for _, info := range emailList {
+ res = append(res, listItem{
+ Id: cast.ToInt64(info.Id),
+ Size: cast.ToInt64(info.Size),
+ })
}
ret := []gopop.UidlItem{}
for _, re := range res {
diff --git a/server/services/auth/auth.go b/server/services/auth/auth.go
index d43077e..ea747e4 100644
--- a/server/services/auth/auth.go
+++ b/server/services/auth/auth.go
@@ -20,14 +20,14 @@ func HasAuth(ctx *context.Context, email *models.Email) bool {
if ctx.IsAdmin {
return true
}
- var ue *models.UserEmail
- err := db.Instance.Where("email_id = ?", email.Id).Find(&ue)
+ var ue []models.UserEmail
+ err := db.Instance.Table(&models.UserEmail{}).Where("email_id = ? and user_id = ?", email.Id, ctx.UserID).Find(&ue)
if err != nil {
log.Errorf("Error while checking user: %v", err)
return false
}
- return ue != nil
+ return len(ue) != 0
}
func DkimGen() string {
diff --git a/server/smtp_server/read_content.go b/server/smtp_server/read_content.go
index 246915e..46e3ab0 100644
--- a/server/smtp_server/read_content.go
+++ b/server/smtp_server/read_content.go
@@ -46,7 +46,7 @@ func (s *Session) Data(r io.Reader) error {
}
log.WithContext(ctx).Debugf("开始执行插件ReceiveParseBefore End!")
- email := parsemail.NewEmailFromReader(s.To, bytes.NewReader(emailData))
+ email := parsemail.NewEmailFromReader(s.To, bytes.NewReader(emailData), len(emailData))
if s.From != "" {
from := parsemail.BuilderUser(s.From)
@@ -209,24 +209,26 @@ func saveEmail(ctx *context.Context, size int, email *parsemail.Email, sendUserI
}
modelEmail := models.Email{
- Type: cast.ToInt8(emailType),
- Subject: email.Subject,
- ReplyTo: json2string(email.ReplyTo),
- FromName: email.From.Name,
- FromAddress: email.From.EmailAddress,
- To: json2string(email.To),
- Bcc: json2string(email.Bcc),
- Cc: json2string(email.Cc),
- Text: sql.NullString{String: string(email.Text), Valid: true},
- Html: sql.NullString{String: string(email.HTML), Valid: true},
- Sender: json2string(email.Sender),
- Attachments: json2string(email.Attachments),
- SPFCheck: spfV,
- DKIMCheck: dkimV,
- SendUserID: sendUserID,
- SendDate: time.Now(),
- Status: cast.ToInt8(email.Status),
- CreateTime: time.Now(),
+ Type: cast.ToInt8(emailType),
+ Subject: email.Subject,
+ ReplyTo: json2string(email.ReplyTo),
+ FromName: email.From.Name,
+ FromAddress: email.From.EmailAddress,
+ To: json2string(email.To),
+ Bcc: json2string(email.Bcc),
+ Cc: json2string(email.Cc),
+ Text: sql.NullString{String: string(email.Text), Valid: true},
+ Html: sql.NullString{String: string(email.HTML), Valid: true},
+ Sender: json2string(email.Sender),
+ Attachments: json2string(email.Attachments),
+ Size: email.Size,
+ SPFCheck: spfV,
+ DKIMCheck: dkimV,
+ SendUserID: sendUserID,
+ SendDate: time.Now(),
+ Status: cast.ToInt8(email.Status),
+ CreateTime: time.Now(),
+ CronSendTime: time.Now(),
}
_, err := db.Instance.Insert(&modelEmail)