mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
v2.6.2 (#177)
1、修复邮件插入失败 2、插件支持设置页面 3、修复pop3邮箱拉取权限判断 4、修复非管理员账户的附件权限判断 Co-authored-by: jinnrry <i@jinnrry.com>
This commit is contained in:
parent
729eb9658a
commit
e6a56b199d
@ -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/
|
||||
|
||||
|
@ -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/
|
||||
|
||||
|
8
Makefile
8
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/
|
||||
|
||||
|
@ -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
fe/src/components/PluginSettings.vue
Normal file
35
fe/src/components/PluginSettings.vue
Normal file
@ -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>
|
@ -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": "发件",
|
||||
|
@ -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
server/controllers/plugin.go
Normal file
46
server/controllers/plugin.go
Normal file
@ -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", "")
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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的容器名称
|
||||
|
||||
# 模型效果
|
||||
|
||||
|
@ -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
|
@ -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
server/hooks/spam_block/static/index.html
Normal file
53
server/hooks/spam_block/static/index.html
Normal file
@ -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>
|
2
server/hooks/spam_block/static/jquery.js
vendored
Normal file
2
server/hooks/spam_block/static/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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)
|
||||
|
File diff suppressed because one or more lines are too long
57
server/hooks/spam_block/trec07p_format.py
Normal file
57
server/hooks/spam_block/trec07p_format.py
Normal file
@ -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)
|
@ -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) {
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
```
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user