* 插件架构改造

* v2.4.0
This commit is contained in:
Jinnrry
2024-03-01 19:10:56 +08:00
committed by GitHub
parent d358812afb
commit 20f3590a04
26 changed files with 629 additions and 128 deletions

View File

@@ -34,28 +34,46 @@ jobs:
- name: Setup Go environment - name: Setup Go environment
uses: actions/setup-go@v4.1.0 uses: actions/setup-go@v4.1.0
with: with:
go-version: '1.21'
check-latest: true check-latest: true
- name: Gen output name - 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 - name: Rename Windows File
if: matrix.goos == 'windows' 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 - name: FE Build
run: cd fe && yarn && yarn build run: cd fe && yarn && yarn build
- name: BE Build - name: BE Build
run: | run: |
cd server && cp -rf ../fe/dist http_server 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 - name: Upload files to Artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.FILENAME }} 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 - name: Upload binaries to release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} 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 }} tag: ${{ github.ref }}
file_glob: true file_glob: true

4
.gitignore vendored
View File

@@ -2,4 +2,6 @@
.DS_Store .DS_Store
dist dist
output output
pmail.db pmail.db
server/plugins
config

View File

@@ -15,6 +15,9 @@ COPY --from=febuild /work/dist /work/http_server/dist
RUN apk update && apk add git 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 "-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 FROM alpine
@@ -30,6 +33,8 @@ RUN apk add --no-cache tzdata \
COPY --from=serverbuild /work/pmail . 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 CMD /work/pmail

View File

@@ -5,7 +5,10 @@ WORKDIR /work
COPY server . COPY server .
RUN apk update && apk add git 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 FROM alpine
@@ -21,6 +24,8 @@ RUN apk add --no-cache tzdata \
COPY --from=serverbuild /work/pmail . 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 CMD /work/pmail

View File

@@ -1,8 +1,9 @@
build: build_fe build_server package build: build_fe build_server telegram_push web_push wechat_push package
clean: clean:
rm -rf output rm -rf output
build_fe: build_fe:
cd fe && yarn && yarn build cd fe && yarn && yarn build
cd server && cp -rf ../fe/dist http_server 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=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 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 package: clean
mkdir output mkdir output
mv server/pmail* output/ mv server/pmail* output/
mkdir config mkdir output/config
mkdir output/plugins
cp -r server/config/dkim output/config/ cp -r server/config/dkim output/config/
cp -r server/config/ssl output/config/ cp -r server/config/ssl output/config/
cp -r server/config/config.json 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/ cp README.md output/

View File

@@ -71,19 +71,6 @@ configure.
Check if your mailbox has completed all the security configuration. It is recommended to 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. 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 # 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 "httpPort": 80, // http port . default 80
"httpsPort": 443, // https port . default 443 "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 "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. "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) 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 # For Developer
## Project Framework ## Project Framework
@@ -143,3 +132,4 @@ The code is in `server` folder.
## Plugin Development ## Plugin Development
Reference this file. `server/hooks/wechat_push/wechat_push.go` Reference this file. `server/hooks/wechat_push/wechat_push.go`

View File

@@ -77,18 +77,6 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
建议找一下邮箱测试服务(比如[https://www.mail-tester.com/](https://www.mail-tester.com/))进行邮件得分检测,避免自己某些步骤漏配,导致发件进对方垃圾箱。 建议找一下邮箱测试服务(比如[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 ```json
@@ -106,14 +94,6 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
"spamFilterLevel": 0,// 垃圾邮件过滤级别0不过滤、1 spf dkim 校验均失败时过滤2 spf校验不通过时过滤 "spamFilterLevel": 0,// 垃圾邮件过滤级别0不过滤、1 spf dkim 校验均失败时过滤2 spf校验不通过时过滤
"httpPort": 80, // http 端口 . 默认 80 "httpPort": 80, // http 端口 . 默认 80
"httpsPort": 443, // https 端口 . 默认 443 "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的时候会进入安装引导流程 "isInit": true // 为false的时候会进入安装引导流程
} }
``` ```
@@ -128,6 +108,15 @@ SMTP地址 smtp.[你的域名]
SMTP端口 25/465(SSL) SMTP端口 25/465(SSL)
# 插件
[微信推送](server/hooks/wechat_push/README.md)
[Telegram推送](server/hooks/wechat_push/README.md)
[WebHook推送](server/hooks/wechat_push/README.md)
# 参与开发 # 参与开发
## 项目架构 ## 项目架构

View File

@@ -41,7 +41,7 @@ type Config struct {
//go:embed tables/* //go:embed tables/*
var tableConfig embed.FS var tableConfig embed.FS
const Version = "2.3.8" const Version = "2.4.0"
const DBTypeMySQL = "mysql" const DBTypeMySQL = "mysql"
const DBTypeSQLite = "sqlite" const DBTypeSQLite = "sqlite"

View File

@@ -11,14 +11,6 @@
"spamFilterLevel": 2, "spamFilterLevel": 2,
"httpPort": 80, "httpPort": 80,
"httpsPort": 443, "httpsPort": 443,
"weChatPushAppId": "",
"weChatPushSecret": "",
"weChatPushTemplateId": "",
"weChatPushUserId": "",
"tgChatId": "",
"tgBotToken": "",
"webPushUrl": "",
"webPushToken": "",
"isInit": true, "isInit": true,
"httpsEnabled": 1 "httpsEnabled": 1
} }

View File

@@ -11,6 +11,7 @@ import (
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/dto/response" "pmail/dto/response"
"pmail/hooks" "pmail/hooks"
"pmail/hooks/framework"
"pmail/i18n" "pmail/i18n"
"pmail/utils/async" "pmail/utils/async"
"pmail/utils/context" "pmail/utils/context"
@@ -134,7 +135,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
continue continue
} }
as.WaitProcess(func(hk any) { as.WaitProcess(func(hk any) {
hk.(hooks.EmailHook).SendBefore(ctx, e) hk.(framework.EmailHook).SendBefore(ctx, e)
}, hook) }, hook)
} }
as.Wait() as.Wait()
@@ -180,7 +181,7 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
continue continue
} }
as2.WaitProcess(func(hk any) { as2.WaitProcess(func(hk any) {
hk.(hooks.EmailHook).SendAfter(ctx, e, sendErr) hk.(framework.EmailHook).SendAfter(ctx, e, sendErr)
}, hook) }, hook)
} }
as2.Wait() as2.Wait()

View File

@@ -1,32 +1,185 @@
package hooks package hooks
import ( import (
oContext "context"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"net"
"net/http"
"os"
"path/filepath"
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/hooks/telegram_push" "pmail/hooks/framework"
"pmail/hooks/web_push"
"pmail/hooks/wechat_push"
"pmail/utils/context" "pmail/utils/context"
"strings"
"time"
) )
type EmailHook interface { // HookList
// SendBefore 邮件发送前的数据 var HookList []framework.EmailHook
SendBefore(ctx *context.Context, email *parsemail.Email)
// SendAfter 邮件发送后的数据err是每个收信服务器的错误信息 type HookSender struct {
SendAfter(ctx *context.Context, email *parsemail.Email, err map[string]error) httpc http.Client
// ReceiveParseBefore 接收到邮件,解析之前的原始数据 name string
ReceiveParseBefore(email []byte) socket string
// ReceiveParseAfter 接收到邮件,解析之后的结构化数据
ReceiveParseAfter(email *parsemail.Email)
} }
// HookList func (h *HookSender) SendBefore(ctx *context.Context, email *parsemail.Email) {
var HookList []EmailHook
// Init 这里注册hook对象 dto := framework.HookDTO{
func Init() { Ctx: ctx,
HookList = []EmailHook{ Email: email,
wechat_push.NewWechatPushHook(), }
telegram_push.NewTelegramPushHook(), body, _ := json.Marshal(dto)
web_push.NewWebPushHook(),
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
}
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() {
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
})
}

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
package telegram_push package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"pmail/config" "pmail/config"
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/hooks/framework"
"pmail/utils/context" "pmail/utils/context"
"strings" "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 == "" { if w.chatId == "" || w.botToken == "" {
return 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 { 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{ ret := &TelegramPushHook{
botToken: config.Instance.TgBotToken, botToken: token,
chatId: config.Instance.TgChatId, chatId: chatID,
webDomain: config.Instance.WebDomain, webDomain: mainConfig.WebDomain,
httpsEnabled: config.Instance.HttpsEnabled, httpsEnabled: mainConfig.HttpsEnabled,
} }
return ret return ret
} }
func main() {
framework.CreatePlugin(NewTelegramPushHook()).Run()
}

View File

@@ -1,4 +1,4 @@
package telegram_push package main
import ( import (
"pmail/config" "pmail/config"

View File

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

View File

@@ -1,11 +1,13 @@
package web_push package main
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os"
"pmail/config" "pmail/config"
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/hooks/framework"
"pmail/utils/context" "pmail/utils/context"
log "github.com/sirupsen/logrus" 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 == "" { if w.url == "" {
return return
} }
@@ -63,7 +65,6 @@ func (w *WebPushHook) ReceiveParseAfter(email *parsemail.Email) {
Token: w.token, Token: w.token,
} }
var ctx *context.Context = nil
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)
if err != nil { if err != nil {
@@ -77,12 +78,56 @@ func (w *WebPushHook) ReceiveParseAfter(email *parsemail.Email) {
defer resp.Body.Close() defer resp.Body.Close()
} }
type Config struct {
WebPushUrl string `json:"webPushUrl"`
WebPushToken string `json:"webPushToken"`
}
func NewWebPushHook() *WebPushHook { 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{ ret := &WebPushHook{
url: config.Instance.WebPushUrl, url: pushURL,
token: config.Instance.WebPushToken, token: token,
} }
return ret return ret
} }
func main() {
framework.CreatePlugin(NewWebPushHook()).Run()
}

View File

@@ -1,4 +1,4 @@
package web_push package main
import ( import (
"pmail/config" "pmail/config"
@@ -15,5 +15,5 @@ func TestWebPushHook_ReceiveParseAfter(t *testing.T) {
testInit() testInit()
w := NewWebPushHook() w := NewWebPushHook()
w.ReceiveParseAfter(&parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")})
} }

View File

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

View File

@@ -1,4 +1,4 @@
package wechat_push package main
import ( import (
"encoding/json" "encoding/json"
@@ -7,8 +7,10 @@ import (
"github.com/spf13/cast" "github.com/spf13/cast"
"io" "io"
"net/http" "net/http"
"os"
"pmail/config" "pmail/config"
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/hooks/framework"
"pmail/utils/context" "pmail/utils/context"
"strings" "strings"
"time" "time"
@@ -26,6 +28,7 @@ type WeChatPushHook struct {
tokenExpires int64 tokenExpires int64
templateId string templateId string
pushUser string pushUser string
mainConfig *config.Config
} }
func (w *WeChatPushHook) SendBefore(ctx *context.Context, email *parsemail.Email) { 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 == "" { if w.appId == "" || w.secret == "" || w.pushUser == "" {
return return
} }
@@ -88,8 +91,8 @@ type DataItem struct {
func (w *WeChatPushHook) sendUserMsg(ctx *context.Context, userId string, content string) { func (w *WeChatPushHook) sendUserMsg(ctx *context.Context, userId string, content string) {
url := config.Instance.WebDomain url := w.mainConfig.WebDomain
if config.Instance.HttpsEnabled > 1 { if w.mainConfig.HttpsEnabled > 1 {
url = "http://" + url url = "http://" + url
} else { } else {
url = "https://" + url 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 { 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{ ret := &WeChatPushHook{
appId: config.Instance.WeChatPushAppId, appId: appid,
secret: config.Instance.WeChatPushSecret, secret: secret,
templateId: config.Instance.WeChatPushTemplateId, templateId: templateId,
pushUser: config.Instance.WeChatPushUserId, pushUser: userId,
mainConfig: mainConfig,
} }
return ret return ret
} }
// 插件将以独立进程运行,因此需要主函数。
func main() {
framework.CreatePlugin(NewWechatPushHook()).Run()
}

View File

@@ -1,4 +1,4 @@
package wechat_push package main
import ( import (
"pmail/config" "pmail/config"
@@ -15,5 +15,5 @@ func TestWeChatPushHook_ReceiveParseAfter(t *testing.T) {
testInit() testInit()
w := NewWechatPushHook() w := NewWechatPushHook()
w.ReceiveParseAfter(&parsemail.Email{Subject: "标题", Text: []byte("文本内容")}) w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")})
} }

View File

@@ -1,11 +1,11 @@
package http_server package http_server
import ( import (
"flag"
"fmt" "fmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io/fs" "io/fs"
"net/http" "net/http"
"pmail/config"
"pmail/controllers" "pmail/controllers"
"time" "time"
) )
@@ -27,9 +27,8 @@ func SetupStart() {
mux.HandleFunc("/.well-known/", controllers.AcmeChallenge) mux.HandleFunc("/.well-known/", controllers.AcmeChallenge)
HttpPort := 80 HttpPort := 80
if config.Instance != nil && config.Instance.HttpPort > 0 { flag.IntVar(&HttpPort, "p", 80, "初始化阶段Http服务端口")
HttpPort = config.Instance.HttpPort flag.Parse()
}
log.Infof("HttpServer Start On Port :%d", HttpPort) log.Infof("HttpServer Start On Port :%d", HttpPort)
setupServer = &http.Server{ setupServer = &http.Server{
Addr: fmt.Sprintf(":%d", HttpPort), Addr: fmt.Sprintf(":%d", HttpPort),

View File

@@ -38,6 +38,7 @@ var (
gitHash string gitHash string
buildTime string buildTime string
goVersion string goVersion string
version string
) )
func main() { func main() {
@@ -74,7 +75,7 @@ func main() {
} }
log.Infoln("***************************************************") 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("***\tGit Commit Hash: %s ", gitHash)
log.Infof("***\tBuild TimeStamp: %s ", buildTime) log.Infof("***\tBuild TimeStamp: %s ", buildTime)
log.Infof("***\tBuild GoLang Version: %s ", goVersion) log.Infof("***\tBuild GoLang Version: %s ", goVersion)

View File

@@ -12,6 +12,7 @@ import (
"pmail/db" "pmail/db"
"pmail/dto/parsemail" "pmail/dto/parsemail"
"pmail/hooks" "pmail/hooks"
"pmail/hooks/framework"
"pmail/services/rule" "pmail/services/rule"
"pmail/utils/async" "pmail/utils/async"
"pmail/utils/context" "pmail/utils/context"
@@ -38,7 +39,7 @@ func (s *Session) Data(r io.Reader) error {
continue continue
} }
as1.WaitProcess(func(hk any) { as1.WaitProcess(func(hk any) {
hk.(hooks.EmailHook).ReceiveParseBefore(emailData) hk.(framework.EmailHook).ReceiveParseBefore(ctx, &emailData)
}, hook) }, hook)
} }
as1.Wait() as1.Wait()
@@ -94,7 +95,7 @@ func (s *Session) Data(r io.Reader) error {
continue continue
} }
as2.WaitProcess(func(hk any) { as2.WaitProcess(func(hk any) {
hk.(hooks.EmailHook).ReceiveParseAfter(email) hk.(framework.EmailHook).ReceiveParseAfter(ctx, email)
}, hook) }, hook)
} }
as2.Wait() as2.Wait()

View File

@@ -11,6 +11,7 @@ import (
"pmail/config" "pmail/config"
"pmail/db" "pmail/db"
parsemail2 "pmail/dto/parsemail" parsemail2 "pmail/dto/parsemail"
"pmail/hooks"
"pmail/session" "pmail/session"
"pmail/utils/context" "pmail/utils/context"
"testing" "testing"
@@ -42,7 +43,7 @@ func testInit() {
parsemail2.Init() parsemail2.Init()
db.Init() db.Init()
session.Init() session.Init()
hooks.Init()
} }
func TestNuisanace(t *testing.T) { func TestNuisanace(t *testing.T) {
@@ -129,9 +130,9 @@ Content-Type: text/html
s := Session{ s := Session{
RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)), RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
Ctx: &context.Context{ Ctx: &context.Context{
UserID: 1, UserID: 0,
UserName: "a", UserName: "",
UserAccount: "a", UserAccount: "",
}, },
} }

View File

@@ -13,21 +13,21 @@ type Context struct {
UserID int UserID int
UserAccount string UserAccount string
UserName string UserName string
values map[string]any Values map[string]any
Lang string Lang string
} }
func (c *Context) SetValue(key string, value any) { func (c *Context) SetValue(key string, value any) {
if c.values == nil { if c.Values == nil {
c.values = map[string]any{} c.Values = map[string]any{}
} }
c.values[key] = value c.Values[key] = value
} }
func (c *Context) GetValue(key string) any { func (c *Context) GetValue(key string) any {
if c.values == nil { if c.Values == nil {
return nil return nil
} }
return c.values[key] return c.Values[key]
} }