From 20f3590a04c1102d6f16f4d9689d4583bff328bc Mon Sep 17 00:00:00 2001 From: Jinnrry Date: Fri, 1 Mar 2024 19:10:56 +0800 Subject: [PATCH] V2.4.0 (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 插件架构改造 * v2.4.0 --- .github/workflows/release.yml | 30 ++- .gitignore | 4 +- Dockerfile | 7 +- DockerfileGithubAction | 9 +- Makefile | 30 ++- README.md | 32 +-- README_CN.md | 29 +-- server/config/config.go | 2 +- server/config/config.json | 8 - server/controllers/email/send.go | 5 +- server/hooks/base.go | 191 ++++++++++++++++-- server/hooks/framework/framework.go | 122 +++++++++++ server/hooks/telegram_push/README.md | 15 ++ server/hooks/telegram_push/telegram_push.go | 62 +++++- .../hooks/telegram_push/telegram_push_test.go | 2 +- server/hooks/web_push/README.md | 14 ++ server/hooks/web_push/web_push.go | 57 +++++- server/hooks/web_push/wechat_push_test.go | 4 +- server/hooks/wechat_push/README.md | 15 ++ server/hooks/wechat_push/wechat_push.go | 77 ++++++- server/hooks/wechat_push/wechat_push_test.go | 4 +- server/http_server/setup_server.go | 7 +- server/main.go | 3 +- server/smtp_server/read_content.go | 5 +- server/smtp_server/read_content_test.go | 9 +- server/utils/context/context.go | 12 +- 26 files changed, 628 insertions(+), 127 deletions(-) create mode 100644 server/hooks/framework/framework.go create mode 100644 server/hooks/telegram_push/README.md create mode 100644 server/hooks/web_push/README.md create mode 100644 server/hooks/wechat_push/README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12e80fc..2d0d796 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,28 +34,46 @@ jobs: - name: Setup Go environment uses: actions/setup-go@v4.1.0 with: - go-version: '1.21' check-latest: true - name: Gen output name - run: echo "FILENAME=pmail_${{ matrix.goos }}_${{ matrix.goarch }}" >> ${GITHUB_ENV} + run: | + echo "FILENAME=pmail_${{ matrix.goos }}_${{ matrix.goarch }}" >> ${GITHUB_ENV} + echo "TGFILENAME=telegram_push_${{ matrix.goos }}_${{ matrix.goarch }}" >> ${GITHUB_ENV} + echo "WCFILENAME=wechat_push_${{ matrix.goos }}_${{ matrix.goarch }}" >> ${GITHUB_ENV} + echo "WEBFILENAME=web_push_${{ matrix.goos }}_${{ matrix.goarch }}" >> ${GITHUB_ENV} - name: Rename Windows File if: matrix.goos == 'windows' - run: echo "FILENAME=pmail_${{ matrix.goos }}_${{ matrix.goarch }}.exe" >> ${GITHUB_ENV} + run: | + echo "FILENAME=pmail_${{ matrix.goos }}_${{ matrix.goarch }}.exe" >> ${GITHUB_ENV} + echo "TGFILENAME=telegram_push_${{ matrix.goos }}_${{ matrix.goarch }}.exe" >> ${GITHUB_ENV} + echo "WCFILENAME=wechat_push_${{ matrix.goos }}_${{ matrix.goarch }}.exe" >> ${GITHUB_ENV} + echo "WEBFILENAME=web_push_${{ matrix.goos }}_${{ matrix.goarch }}.exe" >> ${GITHUB_ENV} - name: FE Build run: cd fe && yarn && yarn build - name: BE Build run: | cd server && cp -rf ../fe/dist http_server - go build -ldflags "-s -w -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 ${{ env.FILENAME }} main.go + 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 ${{ env.FILENAME }} main.go + go build -ldflags "-s -w" -o ${{ env.TGFILENAME }} hooks/telegram_push/telegram_push.go + go build -ldflags "-s -w" -o ${{ env.WEBFILENAME }} hooks/web_push/web_push.go + go build -ldflags "-s -w" -o ${{ env.WCFILENAME }} hooks/wechat_push/wechat_push.go - name: Upload files to Artifacts uses: actions/upload-artifact@v3 with: name: ${{ env.FILENAME }} - path: ./server/${{ env.FILENAME }} + path: | + ./server/${{ env.FILENAME }} + ./server/${{ env.TGFILENAME }} + ./server/${{ env.WEBFILENAME }} + ./server/${{ env.WCFILENAME }} - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ./server/${{ env.FILENAME }} + file: | + ./server/${{ env.FILENAME }} + ./server/${{ env.TGFILENAME }} + ./server/${{ env.WEBFILENAME }} + ./server/${{ env.WCFILENAME }} tag: ${{ github.ref }} file_glob: true diff --git a/.gitignore b/.gitignore index 7f94dad..01f21f4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .DS_Store dist output -pmail.db \ No newline at end of file +pmail.db +server/plugins +config \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7514874..508ec75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,9 @@ COPY --from=febuild /work/dist /work/http_server/dist RUN apk update && apk add git RUN go build -ldflags "-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/hooks/telegram_push && go build -ldflags "-s -W" -o output/telegram_push main.go +RUN cd /work/hooks/web_push && go build -ldflags "-s -W" -o output/web_push main.go +RUN cd /work/hooks/wechat_push && go build -ldflags "-s -W" -o output/wechat_push main.go FROM alpine @@ -30,6 +33,8 @@ RUN apk add --no-cache tzdata \ COPY --from=serverbuild /work/pmail . - +COPY --from=serverbuild /work/hooks/telegram_push/output/* ./plugin/ +COPY --from=serverbuild /work/hooks/web_push/output/* ./plugin/ +COPY --from=serverbuild /work/hooks/wechat_push/output/* ./plugin/ CMD /work/pmail diff --git a/DockerfileGithubAction b/DockerfileGithubAction index 922056b..9cfc5db 100644 --- a/DockerfileGithubAction +++ b/DockerfileGithubAction @@ -5,7 +5,10 @@ WORKDIR /work COPY server . RUN apk update && apk add git -RUN go build -ldflags "-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 go build -ldflags "-s -w -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/hooks/telegram_push && go build -ldflags "-s -W" -o output/telegram_push main.go +RUN cd /work/hooks/web_push && go build -ldflags "-s -W" -o output/web_push main.go +RUN cd /work/hooks/wechat_push && go build -ldflags "-s -W" -o output/wechat_push main.go FROM alpine @@ -21,6 +24,8 @@ RUN apk add --no-cache tzdata \ COPY --from=serverbuild /work/pmail . - +COPY --from=serverbuild /work/hooks/telegram_push/output/* ./plugin/ +COPY --from=serverbuild /work/hooks/web_push/output/* ./plugin/ +COPY --from=serverbuild /work/hooks/wechat_push/output/* ./plugin/ CMD /work/pmail diff --git a/Makefile b/Makefile index 24e973c..1f1e92c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ -build: build_fe build_server package +build: build_fe build_server telegram_push web_push wechat_push package clean: rm -rf output + build_fe: cd fe && yarn && yarn build cd server && cp -rf ../fe/dist http_server @@ -13,11 +14,36 @@ build_server: cd server && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -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_mac_amd64 main.go cd server && CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w -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_mac_arm64 main.go +telegram_push: + cd server/hooks/telegram_push && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o output/telegram_push_linux_amd64 telegram_push.go + cd server/hooks/telegram_push && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o output/telegram_push_windows_amd64.exe telegram_push.go + 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 + cd server/hooks/wechat_push && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o output/wechat_push_windows_amd64.exe wechat_push.go + cd server/hooks/wechat_push && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o output/wechat_push_mac_amd64 wechat_push.go + cd server/hooks/wechat_push && CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o output/wechat_push_mac_arm64 wechat_push.go + +plugin: telegram_push wechat_push web_push + + package: clean mkdir output mv server/pmail* output/ - mkdir config + mkdir output/config + mkdir output/plugins cp -r server/config/dkim output/config/ 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/README.md b/README.md index cbc3a7d..e0c4110 100644 --- a/README.md +++ b/README.md @@ -71,19 +71,6 @@ configure. Check if your mailbox has completed all the security configuration. It is recommended to use [https://www.mail-tester.com/](https://www.mail-tester.com/) for checking. -## 5、 WeChat Message Push - -Open the `config/config.json` file in the run directory, edit a few configuration items at the beginning of `weChatPush` -and restart the service. - -## 6、Telegram Message Push - -Create bot and get token from [BotFather](https://t.me/BotFather) -Open the `config/config.json` file in the run directory, edit a few configuration items at the beginning of `tg`and restart the service. - -## 7、WebHook Push - -Open the `config/config.json` file in the running directory, edit the webPushUrl and webPushToken (optional). After receiving an email, the email information will be posted to the hook address, and the token will also be placed in the body for easy verification. After configuring, restart the service. # Configuration file format description @@ -102,14 +89,6 @@ Open the `config/config.json` file in the running directory, edit the webPushUrl "httpPort": 80, // http port . default 80 "httpsPort": 443, // https port . default 443 "spamFilterLevel": 0,// Spam filter level, 0: no filter, 1: filtering when `spf` and `dkim` don't pass, 2: filtering when `spf` don't pass - "weChatPushAppId": "", // wechat appid - "weChatPushSecret": "", // weChat Secret - "weChatPushTemplateId": "", // weChat TemplateId - "weChatPushUserId": "", // weChat UserId - "tgChatId": "", // telegram chatid - "tgBotToken": "", // telegram token - "webPushUrl": "", // webhook push URL - "webPushToken": "", // webhook push token "isInit": true // If false, it will enter the bootstrap process. } ``` @@ -124,6 +103,16 @@ SMTP Server Address : smtp.[Your Domain] SMTP Port: 25/465(SSL) +# Plugin + +[WeChat Push](server/hooks/wechat_push/README.md) + +[Telegram Push](server/hooks/wechat_push/README.md) + +[Web Push](server/hooks/wechat_push/README.md) + + + # For Developer ## Project Framework @@ -143,3 +132,4 @@ The code is in `server` folder. ## Plugin Development Reference this file. `server/hooks/wechat_push/wechat_push.go` + diff --git a/README_CN.md b/README_CN.md index ab66279..6621618 100644 --- a/README_CN.md +++ b/README_CN.md @@ -77,18 +77,6 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱 建议找一下邮箱测试服务(比如[https://www.mail-tester.com/](https://www.mail-tester.com/))进行邮件得分检测,避免自己某些步骤漏配,导致发件进对方垃圾箱。 -## 5、微信推送 - -打开运行目录下的 `config/config.json`文件,编辑 `weChatPush` 开头的几个配置项,重启服务即可。 - -## 6、Telegram推送 - -从 [BotFather](https://t.me/BotFather) 创建并获取令牌机器人。 打开运行目录下的 config/config.json 文件,编辑 `tg` 开头的几个配置项,重启服务即可。 - -## 7、WebHook推送 - -打开运行目录下的 `config/config.json`文件,编辑 webPushUrl 跟webPushToken (可选),接收到邮件后会往hook地址post发送邮件信息,token也会放在body中,方便需要的进行校验,配置完重启服务即可。 - # 配置文件说明 ```json @@ -106,14 +94,6 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱 "spamFilterLevel": 0,// 垃圾邮件过滤级别,0不过滤、1 spf dkim 校验均失败时过滤,2 spf校验不通过时过滤 "httpPort": 80, // http 端口 . 默认 80 "httpsPort": 443, // https 端口 . 默认 443 - "weChatPushAppId": "", // 微信推送appid - "weChatPushSecret": "", // 微信推送秘钥 - "weChatPushTemplateId": "", // 微信推送模板id - "weChatPushUserId": "", // 微信推送用户id - "tgChatId": "", // telegram 推送chatid - "tgBotToken": "", // telegram 推送 token - "webPushUrl": "", // webhook 推送地址 - "webPushToken": "", // webhook 推送 token "isInit": true // 为false的时候会进入安装引导流程 } ``` @@ -128,6 +108,15 @@ SMTP地址: smtp.[你的域名] SMTP端口: 25/465(SSL) + +# 插件 + +[微信推送](server/hooks/wechat_push/README.md) + +[Telegram推送](server/hooks/wechat_push/README.md) + +[WebHook推送](server/hooks/wechat_push/README.md) + # 参与开发 ## 项目架构 diff --git a/server/config/config.go b/server/config/config.go index b6ce9b2..704e991 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -41,7 +41,7 @@ type Config struct { //go:embed tables/* var tableConfig embed.FS -const Version = "2.3.8" +const Version = "2.4.0" const DBTypeMySQL = "mysql" const DBTypeSQLite = "sqlite" diff --git a/server/config/config.json b/server/config/config.json index bf63c9b..1400834 100644 --- a/server/config/config.json +++ b/server/config/config.json @@ -11,14 +11,6 @@ "spamFilterLevel": 2, "httpPort": 80, "httpsPort": 443, - "weChatPushAppId": "", - "weChatPushSecret": "", - "weChatPushTemplateId": "", - "weChatPushUserId": "", - "tgChatId": "", - "tgBotToken": "", - "webPushUrl": "", - "webPushToken": "", "isInit": true, "httpsEnabled": 1 } \ No newline at end of file diff --git a/server/controllers/email/send.go b/server/controllers/email/send.go index 07a9256..3ea78b9 100644 --- a/server/controllers/email/send.go +++ b/server/controllers/email/send.go @@ -11,6 +11,7 @@ import ( "pmail/dto/parsemail" "pmail/dto/response" "pmail/hooks" + "pmail/hooks/framework" "pmail/i18n" "pmail/utils/async" "pmail/utils/context" @@ -134,7 +135,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { continue } as.WaitProcess(func(hk any) { - hk.(hooks.EmailHook).SendBefore(ctx, e) + hk.(framework.EmailHook).SendBefore(ctx, e) }, hook) } as.Wait() @@ -180,7 +181,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { continue } as2.WaitProcess(func(hk any) { - hk.(hooks.EmailHook).SendAfter(ctx, e, sendErr) + hk.(framework.EmailHook).SendAfter(ctx, e, sendErr) }, hook) } as2.Wait() diff --git a/server/hooks/base.go b/server/hooks/base.go index aa5c760..2aa68e2 100644 --- a/server/hooks/base.go +++ b/server/hooks/base.go @@ -1,32 +1,185 @@ package hooks import ( + oContext "context" + "encoding/json" + "fmt" + log "github.com/sirupsen/logrus" + "io" + "net" + "net/http" + "os" + "path/filepath" "pmail/dto/parsemail" - "pmail/hooks/telegram_push" - "pmail/hooks/web_push" - "pmail/hooks/wechat_push" + "pmail/hooks/framework" "pmail/utils/context" + "strings" + "time" ) -type EmailHook interface { - // SendBefore 邮件发送前的数据 - SendBefore(ctx *context.Context, email *parsemail.Email) - // SendAfter 邮件发送后的数据,err是每个收信服务器的错误信息 - SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) - // ReceiveParseBefore 接收到邮件,解析之前的原始数据 - ReceiveParseBefore(email []byte) - // ReceiveParseAfter 接收到邮件,解析之后的结构化数据 - ReceiveParseAfter(email *parsemail.Email) +// HookList +var HookList []framework.EmailHook + +type HookSender struct { + httpc http.Client + name string + socket string } -// HookList -var HookList []EmailHook +func (h *HookSender) SendBefore(ctx *context.Context, email *parsemail.Email) { + + dto := framework.HookDTO{ + Ctx: ctx, + Email: email, + } + body, _ := json.Marshal(dto) + + ret, err := h.httpc.Post("http://plugin/SendBefore", "application/json", strings.NewReader(string(body))) + if err != nil { + log.WithContext(ctx).Errorf("[%s] Error! %v", h.name, err) + return + } + + body, _ = io.ReadAll(ret.Body) + json.Unmarshal(body, &dto) + + ctx = dto.Ctx + email = dto.Email +} + +func (h *HookSender) SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) { + dto := framework.HookDTO{ + Ctx: ctx, + Email: email, + ErrMap: err, + } + body, _ := json.Marshal(dto) + + ret, errL := h.httpc.Post("http://plugin/SendAfter", "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) + json.Unmarshal(body, &dto) + + ctx = dto.Ctx + email = dto.Email + err = dto.ErrMap +} + +func (h *HookSender) ReceiveParseBefore(ctx *context.Context, email *[]byte) { + dto := framework.HookDTO{ + Ctx: ctx, + EmailByte: email, + } + body, _ := json.Marshal(dto) + + ret, errL := h.httpc.Post("http://plugin/ReceiveParseBefore", "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) + json.Unmarshal(body, &dto) + + ctx = dto.Ctx + email = dto.EmailByte +} -// Init 这里注册hook对象 +func (h *HookSender) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) { + dto := framework.HookDTO{ + Ctx: ctx, + Email: email, + } + body, _ := json.Marshal(dto) + + ret, errL := h.httpc.Post("http://plugin/ReceiveParseAfter", "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) + json.Unmarshal(body, &dto) + + ctx = dto.Ctx + email = dto.Email +} + +func NewHookSender(socketPath string, name string) *HookSender { + httpc := http.Client{ + Timeout: time.Second * 10, + Transport: &http.Transport{ + DialContext: func(ctx oContext.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", socketPath) + }, + }, + } + return &HookSender{ + httpc: httpc, + socket: socketPath, + name: name, + } +} + +// Init 注册hook对象 func Init() { - HookList = []EmailHook{ - wechat_push.NewWechatPushHook(), - telegram_push.NewTelegramPushHook(), - web_push.NewWebPushHook(), + + env := os.Environ() + procAttr := &os.ProcAttr{ + Env: env, + Files: []*os.File{ + os.Stdin, + os.Stdout, + os.Stderr, + }, } + + root := "./plugins" + + pluginNo := 1 + filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if info != nil && !info.IsDir() && !strings.Contains(info.Name(), ".") { + + socketPath := fmt.Sprintf("%s/%d.socket", root, pluginNo) + + os.Remove(socketPath) + + log.Infof("[%s] Plugin Load", info.Name()) + p, err := os.StartProcess(path, []string{ + info.Name(), + fmt.Sprintf("%d.socket", pluginNo), + }, procAttr) + if err != nil { + panic(err) + } + fmt.Printf("[%s] Plugin Start! PID:%d", info.Name(), p.Pid) + + pluginNo++ + + HookList = append(HookList, NewHookSender(socketPath, info.Name())) + + go func() { + stat, err := p.Wait() + log.Errorf("[%s] Plugin Stop. Error:%v Stat:%v", info.Name(), err, stat.String()) + }() + + for i := 0; i < 5; i++ { + time.Sleep(1 * time.Second) + if _, err := os.Stat(socketPath); err == nil { + break + } + if i == 4 { + panic(fmt.Sprintf("[%s] Start Fail!", info.Name())) + } + } + + } + + return nil + }) + } diff --git a/server/hooks/framework/framework.go b/server/hooks/framework/framework.go new file mode 100644 index 0000000..146623b --- /dev/null +++ b/server/hooks/framework/framework.go @@ -0,0 +1,122 @@ +package framework + +import ( + "encoding/json" + log "github.com/sirupsen/logrus" + "io" + "net" + "net/http" + "os" + "path/filepath" + "pmail/dto/parsemail" + "pmail/utils/context" +) + +type EmailHook interface { + // SendBefore 邮件发送前的数据 + SendBefore(ctx *context.Context, email *parsemail.Email) + // SendAfter 邮件发送后的数据,err是每个收信服务器的错误信息 + SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) + // ReceiveParseBefore 接收到邮件,解析之前的原始数据 + ReceiveParseBefore(ctx *context.Context, email *[]byte) + // ReceiveParseAfter 接收到邮件,解析之后的结构化数据 + ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) +} + +type HookDTO struct { + Ctx *context.Context + Email *parsemail.Email + EmailByte *[]byte + ErrMap map[string]error +} + +type Plugin struct { + hook EmailHook +} + +func CreatePlugin(hook EmailHook) *Plugin { + return &Plugin{ + hook: hook, + } +} + +func (p *Plugin) Run() { + if len(os.Args) < 2 { + panic("Command Params Error!") + } + mux := http.NewServeMux() + + mux.HandleFunc("/SendBefore", func(writer http.ResponseWriter, request *http.Request) { + var hookDTO HookDTO + body, _ := io.ReadAll(request.Body) + err := json.Unmarshal(body, &hookDTO) + if err != nil { + log.Errorf("params error %+v", err) + return + } + p.hook.SendBefore(hookDTO.Ctx, hookDTO.Email) + body, _ = json.Marshal(hookDTO) + writer.Write(body) + }) + mux.HandleFunc("/SendAfter", func(writer http.ResponseWriter, request *http.Request) { + + var hookDTO HookDTO + body, _ := io.ReadAll(request.Body) + err := json.Unmarshal(body, &hookDTO) + if err != nil { + log.Errorf("params error %+v", err) + return + } + p.hook.SendAfter(hookDTO.Ctx, hookDTO.Email, hookDTO.ErrMap) + body, _ = json.Marshal(hookDTO) + writer.Write(body) + }) + mux.HandleFunc("/ReceiveParseBefore", func(writer http.ResponseWriter, request *http.Request) { + + var hookDTO HookDTO + body, _ := io.ReadAll(request.Body) + err := json.Unmarshal(body, &hookDTO) + if err != nil { + log.Errorf("params error %+v", err) + return + } + p.hook.ReceiveParseBefore(hookDTO.Ctx, hookDTO.EmailByte) + body, _ = json.Marshal(hookDTO) + writer.Write(body) + }) + mux.HandleFunc("/ReceiveParseAfter", func(writer http.ResponseWriter, request *http.Request) { + + var hookDTO HookDTO + body, _ := io.ReadAll(request.Body) + err := json.Unmarshal(body, &hookDTO) + if err != nil { + log.Errorf("params error %+v", err) + return + } + p.hook.ReceiveParseAfter(hookDTO.Ctx, hookDTO.Email) + body, _ = json.Marshal(hookDTO) + writer.Write(body) + }) + + server := http.Server{ + Handler: mux, + } + + unixListener, err := net.Listen("unix", getExePath()+"/"+os.Args[1]) + if err != nil { + panic(err) + } + err = server.Serve(unixListener) + if err != nil { + panic(err) + } +} + +func getExePath() string { + ex, err := os.Executable() + if err != nil { + panic(err) + } + exePath := filepath.Dir(ex) + return exePath +} diff --git a/server/hooks/telegram_push/README.md b/server/hooks/telegram_push/README.md new file mode 100644 index 0000000..318cb0b --- /dev/null +++ b/server/hooks/telegram_push/README.md @@ -0,0 +1,15 @@ +## How To Ues + +Create bot and get token from [BotFather](https://t.me/BotFather) + +Copy plugin binary file to `/plugins` + +add config.json to `/plugins/config.com` like this: + +```json +{ + "tgChatId": "", // telegram chatid + "tgBotToken": "", // telegram token +} + +``` \ No newline at end of file diff --git a/server/hooks/telegram_push/telegram_push.go b/server/hooks/telegram_push/telegram_push.go index 10c0c7e..5f0c01e 100644 --- a/server/hooks/telegram_push/telegram_push.go +++ b/server/hooks/telegram_push/telegram_push.go @@ -1,11 +1,13 @@ -package telegram_push +package main import ( "encoding/json" "fmt" "net/http" + "os" "pmail/config" "pmail/dto/parsemail" + "pmail/hooks/framework" "pmail/utils/context" "strings" @@ -27,11 +29,11 @@ func (w *TelegramPushHook) SendAfter(ctx *context.Context, email *parsemail.Emai } -func (w *TelegramPushHook) ReceiveParseBefore(email []byte) { +func (w *TelegramPushHook) ReceiveParseBefore(ctx *context.Context, email *[]byte) { } -func (w *TelegramPushHook) ReceiveParseAfter(email *parsemail.Email) { +func (w *TelegramPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) { if w.chatId == "" || w.botToken == "" { return } @@ -84,13 +86,59 @@ func (w *TelegramPushHook) sendUserMsg(ctx *context.Context, email *parsemail.Em } } + +type Config struct { + TgBotToken string `json:"tgBotToken"` + TgChatId string `json:"tgChatId"` +} + func NewTelegramPushHook() *TelegramPushHook { + 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("./telegram_push_config.json"); err == nil { + cfgData, err = os.ReadFile("./telegram_push_config.json") + if err != nil { + panic(err) + } + err = json.Unmarshal(cfgData, &pluginConfig) + if err != nil { + panic(err) + } + + } + + token := "" + chatID := "" + if pluginConfig != nil { + token = pluginConfig.TgBotToken + chatID = pluginConfig.TgChatId + } else { + token = mainConfig.TgBotToken + chatID = mainConfig.TgChatId + } + ret := &TelegramPushHook{ - botToken: config.Instance.TgBotToken, - chatId: config.Instance.TgChatId, - webDomain: config.Instance.WebDomain, - httpsEnabled: config.Instance.HttpsEnabled, + botToken: token, + chatId: chatID, + webDomain: mainConfig.WebDomain, + httpsEnabled: mainConfig.HttpsEnabled, } return ret } + +func main() { + framework.CreatePlugin(NewTelegramPushHook()).Run() +} diff --git a/server/hooks/telegram_push/telegram_push_test.go b/server/hooks/telegram_push/telegram_push_test.go index ec258dd..4b4a921 100644 --- a/server/hooks/telegram_push/telegram_push_test.go +++ b/server/hooks/telegram_push/telegram_push_test.go @@ -1,4 +1,4 @@ -package telegram_push +package main import ( "pmail/config" diff --git a/server/hooks/web_push/README.md b/server/hooks/web_push/README.md new file mode 100644 index 0000000..fe3f793 --- /dev/null +++ b/server/hooks/web_push/README.md @@ -0,0 +1,14 @@ +## 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 index 400a70c..6fc0345 100644 --- a/server/hooks/web_push/web_push.go +++ b/server/hooks/web_push/web_push.go @@ -1,11 +1,13 @@ -package web_push +package main import ( "bytes" "encoding/json" "net/http" + "os" "pmail/config" "pmail/dto/parsemail" + "pmail/hooks/framework" "pmail/utils/context" log "github.com/sirupsen/logrus" @@ -33,11 +35,11 @@ func (w *WebPushHook) SendAfter(ctx *context.Context, email *parsemail.Email, er } -func (w *WebPushHook) ReceiveParseBefore(email []byte) { +func (w *WebPushHook) ReceiveParseBefore(ctx *context.Context, email *[]byte) { } -func (w *WebPushHook) ReceiveParseAfter(email *parsemail.Email) { +func (w *WebPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) { if w.url == "" { return } @@ -63,7 +65,6 @@ func (w *WebPushHook) ReceiveParseAfter(email *parsemail.Email) { Token: w.token, } - var ctx *context.Context = nil jsonData, err := json.Marshal(data) if err != nil { @@ -77,12 +78,56 @@ func (w *WebPushHook) ReceiveParseAfter(email *parsemail.Email) { defer resp.Body.Close() } +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("./web_push_config.json"); err == nil { + cfgData, err = os.ReadFile("./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: config.Instance.WebPushUrl, - token: config.Instance.WebPushToken, + url: pushURL, + token: token, } return ret } + +func main() { + framework.CreatePlugin(NewWebPushHook()).Run() +} diff --git a/server/hooks/web_push/wechat_push_test.go b/server/hooks/web_push/wechat_push_test.go index bbd7511..09b7771 100644 --- a/server/hooks/web_push/wechat_push_test.go +++ b/server/hooks/web_push/wechat_push_test.go @@ -1,4 +1,4 @@ -package web_push +package main import ( "pmail/config" @@ -15,5 +15,5 @@ func TestWebPushHook_ReceiveParseAfter(t *testing.T) { testInit() w := NewWebPushHook() - w.ReceiveParseAfter(&parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) + w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) } diff --git a/server/hooks/wechat_push/README.md b/server/hooks/wechat_push/README.md new file mode 100644 index 0000000..4c23076 --- /dev/null +++ b/server/hooks/wechat_push/README.md @@ -0,0 +1,15 @@ +## How To Ues + + +Copy plugin binary file to `/plugins` + +add config.json to `/plugins/config.com` like this: + +```json +{ + "weChatPushAppId": "", // wechat appid + "weChatPushSecret": "", // weChat Secret + "weChatPushTemplateId": "", // weChat TemplateId + "weChatPushUserId": "", // weChat UserId +} +``` \ No newline at end of file diff --git a/server/hooks/wechat_push/wechat_push.go b/server/hooks/wechat_push/wechat_push.go index d138aa2..2d3ccf5 100644 --- a/server/hooks/wechat_push/wechat_push.go +++ b/server/hooks/wechat_push/wechat_push.go @@ -1,4 +1,4 @@ -package wechat_push +package main import ( "encoding/json" @@ -7,8 +7,10 @@ import ( "github.com/spf13/cast" "io" "net/http" + "os" "pmail/config" "pmail/dto/parsemail" + "pmail/hooks/framework" "pmail/utils/context" "strings" "time" @@ -26,6 +28,7 @@ type WeChatPushHook struct { tokenExpires int64 templateId string pushUser string + mainConfig *config.Config } func (w *WeChatPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) { @@ -36,11 +39,11 @@ func (w *WeChatPushHook) SendAfter(ctx *context.Context, email *parsemail.Email, } -func (w *WeChatPushHook) ReceiveParseBefore(email []byte) { +func (w *WeChatPushHook) ReceiveParseBefore(ctx *context.Context, email *[]byte) { } -func (w *WeChatPushHook) ReceiveParseAfter(email *parsemail.Email) { +func (w *WeChatPushHook) ReceiveParseAfter(ctx *context.Context, email *parsemail.Email) { if w.appId == "" || w.secret == "" || w.pushUser == "" { return } @@ -88,8 +91,8 @@ type DataItem struct { func (w *WeChatPushHook) sendUserMsg(ctx *context.Context, userId string, content string) { - url := config.Instance.WebDomain - if config.Instance.HttpsEnabled > 1 { + url := w.mainConfig.WebDomain + if w.mainConfig.HttpsEnabled > 1 { url = "http://" + url } else { url = "https://" + url @@ -108,14 +111,70 @@ func (w *WeChatPushHook) sendUserMsg(ctx *context.Context, userId string, conten } } + +type Config struct { + WeChatPushAppId string `json:"weChatPushAppId"` + WeChatPushSecret string `json:"weChatPushSecret"` + WeChatPushTemplateId string `json:"weChatPushTemplateId"` + WeChatPushUserId string `json:"weChatPushUserId"` +} + func NewWechatPushHook() *WeChatPushHook { + 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("./wechat_push_config.json"); err == nil { + cfgData, err = os.ReadFile("./wechat_push_config.json") + if err != nil { + panic(err) + } + err = json.Unmarshal(cfgData, &pluginConfig) + if err != nil { + panic(err) + } + + } + + appid := "" + secret := "" + templateId := "" + userId := "" + if pluginConfig != nil { + appid = pluginConfig.WeChatPushAppId + secret = pluginConfig.WeChatPushSecret + templateId = pluginConfig.WeChatPushTemplateId + userId = pluginConfig.WeChatPushUserId + } else { + appid = mainConfig.WeChatPushAppId + secret = mainConfig.WeChatPushSecret + templateId = mainConfig.WeChatPushTemplateId + userId = mainConfig.WeChatPushUserId + } + ret := &WeChatPushHook{ - appId: config.Instance.WeChatPushAppId, - secret: config.Instance.WeChatPushSecret, - templateId: config.Instance.WeChatPushTemplateId, - pushUser: config.Instance.WeChatPushUserId, + appId: appid, + secret: secret, + templateId: templateId, + pushUser: userId, + mainConfig: mainConfig, } return ret } + +// 插件将以独立进程运行,因此需要主函数。 +func main() { + framework.CreatePlugin(NewWechatPushHook()).Run() +} diff --git a/server/hooks/wechat_push/wechat_push_test.go b/server/hooks/wechat_push/wechat_push_test.go index 77ba856..cb5feb8 100644 --- a/server/hooks/wechat_push/wechat_push_test.go +++ b/server/hooks/wechat_push/wechat_push_test.go @@ -1,4 +1,4 @@ -package wechat_push +package main import ( "pmail/config" @@ -15,5 +15,5 @@ func TestWeChatPushHook_ReceiveParseAfter(t *testing.T) { testInit() w := NewWechatPushHook() - w.ReceiveParseAfter(&parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) + w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) } diff --git a/server/http_server/setup_server.go b/server/http_server/setup_server.go index 603c0c4..8ad74a4 100644 --- a/server/http_server/setup_server.go +++ b/server/http_server/setup_server.go @@ -1,11 +1,11 @@ package http_server import ( + "flag" "fmt" log "github.com/sirupsen/logrus" "io/fs" "net/http" - "pmail/config" "pmail/controllers" "time" ) @@ -27,9 +27,8 @@ func SetupStart() { mux.HandleFunc("/.well-known/", controllers.AcmeChallenge) HttpPort := 80 - if config.Instance != nil && config.Instance.HttpPort > 0 { - HttpPort = config.Instance.HttpPort - } + flag.IntVar(&HttpPort, "p", 80, "初始化阶段Http服务端口") + flag.Parse() log.Infof("HttpServer Start On Port :%d", HttpPort) setupServer = &http.Server{ Addr: fmt.Sprintf(":%d", HttpPort), diff --git a/server/main.go b/server/main.go index 12f6323..569f7f9 100644 --- a/server/main.go +++ b/server/main.go @@ -38,6 +38,7 @@ var ( gitHash string buildTime string goVersion string + version string ) func main() { @@ -74,7 +75,7 @@ func main() { } log.Infoln("***************************************************") - log.Infof("***\tServer Start Success Version:%s\n", config.Version) + log.Infof("***\tServer Start Success Version:%s\n", version) log.Infof("***\tGit Commit Hash: %s ", gitHash) log.Infof("***\tBuild TimeStamp: %s ", buildTime) log.Infof("***\tBuild GoLang Version: %s ", goVersion) diff --git a/server/smtp_server/read_content.go b/server/smtp_server/read_content.go index 592a801..592cf8f 100644 --- a/server/smtp_server/read_content.go +++ b/server/smtp_server/read_content.go @@ -12,6 +12,7 @@ import ( "pmail/db" "pmail/dto/parsemail" "pmail/hooks" + "pmail/hooks/framework" "pmail/services/rule" "pmail/utils/async" "pmail/utils/context" @@ -38,7 +39,7 @@ func (s *Session) Data(r io.Reader) error { continue } as1.WaitProcess(func(hk any) { - hk.(hooks.EmailHook).ReceiveParseBefore(emailData) + hk.(framework.EmailHook).ReceiveParseBefore(ctx, &emailData) }, hook) } as1.Wait() @@ -94,7 +95,7 @@ func (s *Session) Data(r io.Reader) error { continue } as2.WaitProcess(func(hk any) { - hk.(hooks.EmailHook).ReceiveParseAfter(email) + hk.(framework.EmailHook).ReceiveParseAfter(ctx, email) }, hook) } as2.Wait() diff --git a/server/smtp_server/read_content_test.go b/server/smtp_server/read_content_test.go index 69e9fcc..da965ff 100644 --- a/server/smtp_server/read_content_test.go +++ b/server/smtp_server/read_content_test.go @@ -11,6 +11,7 @@ import ( "pmail/config" "pmail/db" parsemail2 "pmail/dto/parsemail" + "pmail/hooks" "pmail/session" "pmail/utils/context" "testing" @@ -42,7 +43,7 @@ func testInit() { parsemail2.Init() db.Init() session.Init() - + hooks.Init() } func TestNuisanace(t *testing.T) { @@ -129,9 +130,9 @@ Content-Type: text/html s := Session{ RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)), Ctx: &context.Context{ - UserID: 1, - UserName: "a", - UserAccount: "a", + UserID: 0, + UserName: "", + UserAccount: "", }, } diff --git a/server/utils/context/context.go b/server/utils/context/context.go index c2d898c..f98cb0a 100644 --- a/server/utils/context/context.go +++ b/server/utils/context/context.go @@ -13,21 +13,21 @@ type Context struct { UserID int UserAccount string UserName string - values map[string]any + Values map[string]any Lang string } func (c *Context) SetValue(key string, value any) { - if c.values == nil { - c.values = map[string]any{} + if c.Values == nil { + c.Values = map[string]any{} } - c.values[key] = value + c.Values[key] = value } func (c *Context) GetValue(key string) any { - if c.values == nil { + if c.Values == nil { return nil } - return c.values[key] + return c.Values[key] }