Pārlūkot izejas kodu

v2.6.2 (#177)

1、修复邮件插入失败
2、插件支持设置页面
3、修复pop3邮箱拉取权限判断
4、修复非管理员账户的附件权限判断

Co-authored-by: jinnrry <i@jinnrry.com>
Jinnrry 1 gadu atpakaļ
vecāks
revīzija
e6a56b199d

+ 0 - 2
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/
 

+ 0 - 2
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/
 

+ 1 - 7
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/
 

+ 6 - 0
fe/src/components/HomeHeader.vue

@@ -25,6 +25,11 @@
                 <el-tab-pane v-if="$userInfos.is_admin" :label="lang.user_management">
                     <UserManagement />
                 </el-tab-pane>
+
+                <el-tab-pane :label="lang.plugin_settings">
+                    <PluginSettings />
+                </el-tab-pane>
+
             </el-tabs>
         </el-drawer>
 
@@ -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

+ 35 - 0
fe/src/components/PluginSettings.vue

@@ -0,0 +1,35 @@
+<template>
+    <div id="main">
+        <el-tabs>
+            <el-tab-pane v-for="(src, name) in pluginList" :label="name">
+                <iframe :src="src"></iframe>
+            </el-tab-pane>
+        </el-tabs>
+    </div>
+</template>
+
+<script setup>
+import { reactive, ref, getCurrentInstance } from 'vue'
+const app = getCurrentInstance()
+const $http = app.appContext.config.globalProperties.$http
+const pluginList = reactive({})
+
+$http.get('/api/plugin/list').then(res => {
+    if (res.data != null && res.data.length > 0) {
+        for (let i = 0; i < res.data.length; i++) {
+            let name = res.data[i];
+            pluginList[name] = "/api/plugin/settings/"+ name +"/index.html";
+        }
+    }
+})
+
+</script>
+
+<style scoped>
+
+iframe{
+    width: 100%;
+    border: 0;
+}
+
+</style>

+ 2 - 0
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": "发件",

+ 1 - 1
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,

+ 46 - 0
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", "")
+}

+ 3 - 1
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 {

+ 45 - 2
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)
 			}
 
 		}

+ 37 - 0
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,

+ 3 - 8
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的容器名称
 
 # 模型效果
 

+ 0 - 0
server/hooks/spam_block/Makefile → server/hooks/spam_block/export/Makefile


+ 3 - 1
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

+ 77 - 17
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(`
+<div>
+	Please contact the administrator for configuration.
+</div>
+`)
+		}
+		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()

+ 53 - 0
server/hooks/spam_block/static/index.html

@@ -0,0 +1,53 @@
+<html>
+<header>
+    <script src="/api/plugin/settings/SpamBlock/jquery.js"></script>
+    <script>
+        $(function () {
+            $("#submit").click(function () {
+                let data = {};
+                let value = $('#form').serializeArray();
+                $.each(value, function (index, item) {
+                    data[item.name] = item.value;
+                });
+                let jsonData = JSON.stringify(data);
+                console.log(jsonData);
+
+                $.post("/api/plugin/settings/SpamBlock/save", jsonData, function (data) {
+                    alert(data)
+                })
+            })
+        })
+    </script>
+</header>
+<body>
+
+<div>
+    <form id="form" action="/api/plugin/settings/SpamBlock/save" method="post">
+
+        <div>
+            <label for="url"> 模型API接口地址: </label>
+            <input id="url" style="width: 600px;" name="url" value="%s"
+                   placeholder="http://localhost:8501/v1/models/emotion_model:predict">
+        </div>
+
+        <div>
+            <label for="timeout"> 超时时间: </label>
+            <input id="timeout" style="width: 600px;" name="timeout" value="%d" placeholder="单位毫秒">
+        </div>
+
+        <div>
+            <label for="threshold"> 识别阈值: </label>
+            <input id="threshold" name="threshold" type="number" value="%f" style="width: 600px;"
+                   step=".01" max="100" min="0" placeholder="识别阈值,值越低过滤越严格,越容易误判">
+        </div>
+
+        <input id="submit" type="button" value="Submit"/>
+
+    </form>
+</div>
+
+
+</body>
+
+
+</html>

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
server/hooks/spam_block/static/jquery.js


+ 2 - 0
server/hooks/spam_block/train.py

@@ -24,6 +24,8 @@ def getData(folder_path):
                 # 读取csv文件内容
                 with open(file_path, 'r', errors='ignore') as csv_file:
                     for line in csv_file:
+                        if line[0] == '' or line[0]==' ':
+                            continue
                         labels.append([int(str.strip(line[0]))])
                         msgs.append(line[3:])
     return np.array(msgs), np.array(labels)

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 2
server/hooks/spam_block/trainData/data.csv


+ 57 - 0
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)

+ 14 - 0
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(`
+<div>
+	 TG push No Settings Page
+</div>
+`)
+}
+
 func (w *TelegramPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) {
 
 }

+ 0 - 14
server/hooks/web_push/README.md

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

+ 0 - 136
server/hooks/web_push/web_push.go

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

+ 13 - 0
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(`
+<div>
+	 TG push No Settings Page
+</div>
+`)
+}
+
 func (w *WeChatPushHook) ReceiveSaveAfter(ctx *context.Context, email *parsemail.Email, ue []*models.UserEmail) {
 	if w.appId == "" || w.secret == "" || w.pushUser == "" {
 		return

+ 2 - 0
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() {

+ 6 - 9
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 {

+ 3 - 3
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 {

+ 21 - 19
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)

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels