mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
refactor(msg_gateway): restructure and rename message_gateway aligned with custom_example
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package do defines domain objects, DTOs, and request/response payloads for msg_gateway.
|
||||
package do
|
||||
|
||||
import "time"
|
||||
|
||||
// Capability describes what an adapter can send and receive.
|
||||
type Capability struct {
|
||||
Text bool
|
||||
Image bool
|
||||
File bool
|
||||
Reply bool
|
||||
Group bool
|
||||
}
|
||||
|
||||
// ChannelConfig is the decrypted runtime config passed to a factory.
|
||||
type ChannelConfig struct {
|
||||
ID uint64
|
||||
Type string
|
||||
Name string
|
||||
Credentials map[string]string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// Recipient is the outbound destination on a platform.
|
||||
type Recipient struct {
|
||||
ChatID string
|
||||
PlatformUserID string
|
||||
}
|
||||
|
||||
// Attachment is a downloaded inbound file sitting on local disk.
|
||||
type Attachment struct {
|
||||
Path string
|
||||
FileName string
|
||||
MIME string
|
||||
Error string
|
||||
}
|
||||
|
||||
// InboundMessage is a normalized private-chat message.
|
||||
type InboundMessage struct {
|
||||
ChannelID uint64
|
||||
ChannelType string
|
||||
PlatformUserID string
|
||||
ChatID string
|
||||
MessageID string
|
||||
Text string
|
||||
Attachments []Attachment
|
||||
BindingUserID *uint64
|
||||
}
|
||||
|
||||
// OutboundMessage is a reply or probe send.
|
||||
type OutboundMessage struct {
|
||||
Text string
|
||||
ReplyToID string
|
||||
Attachments []Attachment
|
||||
}
|
||||
|
||||
// BindRequest is the user bind body.
|
||||
type BindRequest struct {
|
||||
ChannelID string `json:"channel_id"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// BindingDTO is a user-facing binding row.
|
||||
type BindingDTO struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
UserID uint64 `json:"user_id,string"`
|
||||
ChannelID uint64 `json:"channel_id,string"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
ChannelType string `json:"channel_type"`
|
||||
PlatformUserID string `json:"platform_user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// PublicChannelDTO is an enabled channel a user can bind to.
|
||||
type PublicChannelDTO struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Field is one admin form field.
|
||||
type Field struct {
|
||||
Key string `json:"key"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
}
|
||||
|
||||
// Definition describes a channel type form.
|
||||
type Definition struct {
|
||||
Type string `json:"type"`
|
||||
Fields []Field `json:"fields"`
|
||||
}
|
||||
|
||||
// ChannelDTO represents a channel for admin consumption.
|
||||
type ChannelDTO struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
OwnerScope string `json:"owner_scope"`
|
||||
OwnerID *uint64 `json:"owner_id,string,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Credentials map[string]string `json:"credentials"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
}
|
||||
|
||||
// CreateChannelRequest is admin create payload.
|
||||
type CreateChannelRequest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Credentials map[string]string `json:"credentials"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
}
|
||||
|
||||
// UpdateChannelRequest is admin update payload.
|
||||
type UpdateChannelRequest struct {
|
||||
Name string `json:"name"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
Credentials map[string]string `json:"credentials"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"Wavelet/plugins/domain/msg_gateway/consts"
|
||||
pkgpush "Wavelet/plugins/domain/msg_gateway/push"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SMTPConfig mirrors the system SMTP settings consumed by the push service.
|
||||
type SMTPConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// PushField represents a form field configuration for a channel.
|
||||
type PushField struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type"`
|
||||
Required bool `json:"required"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// PushDefinition represents the metadata and form schema for a notification channel.
|
||||
type PushDefinition struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Fields []PushField `json:"fields"`
|
||||
}
|
||||
|
||||
// CreatePushChannelRequest is the create channel request payload.
|
||||
type CreatePushChannelRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
Other string `json:"other"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// UpdatePushChannelRequest is the update channel request payload.
|
||||
type UpdatePushChannelRequest struct {
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
Other string `json:"other"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// TestPushChannelRequest is the test channel request payload.
|
||||
type TestPushChannelRequest struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
Other string `json:"other"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// CustomPushRequest contains custom webhook parameters.
|
||||
type CustomPushRequest struct {
|
||||
Title string `json:"title" form:"title"`
|
||||
Description string `json:"description" form:"description"`
|
||||
Content string `json:"content" form:"content"`
|
||||
URL string `json:"url" form:"url"`
|
||||
To string `json:"to" form:"to"`
|
||||
Token string `json:"token" form:"token"`
|
||||
}
|
||||
|
||||
// CreatePushEventRequest is the request body for creating a push event.
|
||||
type CreatePushEventRequest struct {
|
||||
EventKey string `json:"event_key"`
|
||||
TaskType string `json:"task_type"`
|
||||
Channels []string `json:"channels"`
|
||||
Targets []string `json:"targets"`
|
||||
Template string `json:"template"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// UpdatePushEventRequest is the request body for updating a push event.
|
||||
type UpdatePushEventRequest struct {
|
||||
Channels []string `json:"channels"`
|
||||
Targets []string `json:"targets"`
|
||||
Template string `json:"template" binding:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// TestPushRequest is the request body for testing push config.
|
||||
type TestPushRequest struct {
|
||||
Config pkgpush.Config `json:"config" binding:"required"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// NotificationMessage represents the structured notification message payload.
|
||||
type NotificationMessage struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Level string `json:"level"`
|
||||
Ext map[string]any `json:"ext,omitempty"`
|
||||
}
|
||||
|
||||
// Flatten converts the structured NotificationMessage back to a flat map.
|
||||
func (m NotificationMessage) Flatten() map[string]any {
|
||||
res := map[string]any{
|
||||
consts.KeyTitle: m.Title,
|
||||
consts.KeyContent: m.Content,
|
||||
consts.KeyLevel: m.Level,
|
||||
}
|
||||
for k, v := range m.Ext {
|
||||
res[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// EventMetadata represents the metadata of a push notification event.
|
||||
type EventMetadata struct {
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
DefaultTemplate NotificationMessage `json:"default_template"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// SendPayload is the async push dispatch payload consumed by the notification worker.
|
||||
type SendPayload struct {
|
||||
EventKey string `json:"event_key"`
|
||||
Config pkgpush.Config `json:"config"`
|
||||
Target string `json:"target"`
|
||||
Body NotificationMessage `json:"body"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// PushHistoryListFilter filters push history pagination queries.
|
||||
type PushHistoryListFilter struct {
|
||||
EventKey string
|
||||
Channel string
|
||||
Status string
|
||||
StartTime *time.Time
|
||||
EndTime *time.Time
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// PushNotificationEvent defines the payload for eventbus notification trigger.
|
||||
type PushNotificationEvent struct {
|
||||
UserID uint64 `json:"user_id"`
|
||||
Channel string `json:"channel"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
pushDefMu sync.RWMutex
|
||||
pushDefinitions = make(map[string]PushDefinition)
|
||||
)
|
||||
|
||||
// RegisterPushChannelDefinition registers a channel definition.
|
||||
func RegisterPushChannelDefinition(def PushDefinition) {
|
||||
pushDefMu.Lock()
|
||||
defer pushDefMu.Unlock()
|
||||
pushDefinitions[def.Type] = def
|
||||
}
|
||||
|
||||
// ListPushDefinitions returns all registered channel definitions.
|
||||
func ListPushDefinitions() []PushDefinition {
|
||||
pushDefMu.RLock()
|
||||
defer pushDefMu.RUnlock()
|
||||
|
||||
order := []string{
|
||||
consts.ChannelCustom,
|
||||
consts.ChannelLark,
|
||||
consts.ChannelDingTalk,
|
||||
consts.ChannelTelegram,
|
||||
consts.ChannelBark,
|
||||
consts.ChannelDiscord,
|
||||
consts.ChannelSlack,
|
||||
consts.ChannelPushover,
|
||||
consts.ChannelEmail,
|
||||
}
|
||||
res := make([]PushDefinition, 0, len(pushDefinitions))
|
||||
for _, t := range order {
|
||||
if d, ok := pushDefinitions[t]; ok {
|
||||
res = append(res, d)
|
||||
}
|
||||
}
|
||||
for t, d := range pushDefinitions {
|
||||
found := false
|
||||
for _, o := range order {
|
||||
if o == t {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
res = append(res, d)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
//nolint:funlen,goconst,dupl // Channel definitions registration table
|
||||
func init() {
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelCustom,
|
||||
Name: "自定义消息通道",
|
||||
Description: "使用自定义 HTTP POST 请求向外部 Webhook 发送数据。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "请求地址",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "在此填写完整的请求地址,必须使用 HTTPS 协议",
|
||||
Description: "接口请求的完整 HTTPS URL,例如 https://api.example.com/webhook",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyOther,
|
||||
Label: "请求体 (JSON)",
|
||||
Type: consts.TypeTextarea,
|
||||
Required: true,
|
||||
Placeholder: "在此输入请求体,支持模板变量,必须为合法的 JSON 格式",
|
||||
Description: "可使用的变量:$title, $description, $content, $url, $to。例如 {\"text\": \"$content\"}",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelLark,
|
||||
Name: "飞书群机器人",
|
||||
Description: "配置飞书群自定义机器人的 Webhook 接口投递。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "Webhook 地址",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_TOKEN",
|
||||
Description: "从飞书群机器人设置中复制的 Webhook URL",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyToken,
|
||||
Label: "签名校验密钥 (Secret) (可选)",
|
||||
Type: consts.TypeText,
|
||||
Required: false,
|
||||
Placeholder: "可选,若机器人启用了安全设置中的签名校验,请在此输入",
|
||||
Description: "飞书群机器人安全设置中的签名校验 Key",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyOther,
|
||||
Label: "自定义卡片 JSON 模版 (可选)",
|
||||
Type: consts.TypeTextarea,
|
||||
Required: false,
|
||||
Placeholder: "可选,留空则默认使用系统内置的精美互动卡片",
|
||||
Description: "若填写,必须是合法的飞书卡片 JSON 格式",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelDingTalk,
|
||||
Name: "钉钉群机器人",
|
||||
Description: "配置钉钉群自定义机器人的 Webhook 接口投递。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "Webhook 地址",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN",
|
||||
Description: "从钉钉群机器人设置中获取的完整 Webhook URL",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyToken,
|
||||
Label: "加签密钥 (Secret) (可选)",
|
||||
Type: consts.TypeText,
|
||||
Required: false,
|
||||
Placeholder: "可选,若机器人启用了安全设置中的加签校验,请在此输入 SEC 开头的密钥",
|
||||
Description: "钉钉群机器人安全设置中的加签 Secret",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelTelegram,
|
||||
Name: "Telegram 机器人",
|
||||
Description: "配置 Telegram 机器人推送消息。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "API 基础地址 (可选)",
|
||||
Type: consts.TypeText,
|
||||
Required: false,
|
||||
Placeholder: "https://api.telegram.org",
|
||||
Description: "接口请求的 HTTPS 基础地址,留空默认为 https://api.telegram.org",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyToken,
|
||||
Label: "机器人 Token (Bot Token)",
|
||||
Type: consts.TypePassword,
|
||||
Required: true,
|
||||
Placeholder: "在此输入 Telegram 机器人的 Bot Token",
|
||||
Description: "通过 BotFather 申请到的机器人 Access Token",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyOther,
|
||||
Label: "默认会话 ID (Chat ID) (可选)",
|
||||
Type: consts.TypeText,
|
||||
Required: false,
|
||||
Placeholder: "例如 -100123456789 或 @channel_name",
|
||||
Description: "默认的消息接收 Chat ID。如果通知事件中未配置 targets,将推送到此 ID",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelBark,
|
||||
Name: "Bark (iOS 推送)",
|
||||
Description: "配置 Bark 推送通知至 iPhone / iPad 客户端。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyToken,
|
||||
Label: "设备 Key (Device Key)",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "Bark App 首页显示的 Device Key",
|
||||
Description: "从 Bark App 复制的设备专属 Key",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "Bark 服务器地址 (可选)",
|
||||
Type: consts.TypeText,
|
||||
Required: false,
|
||||
Placeholder: "https://api.day.app",
|
||||
Description: "Bark 服务器地址,留空默认使用官方公共服务器 https://api.day.app",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyOther,
|
||||
Label: "额外配置 JSON (可选)",
|
||||
Type: consts.TypeTextarea,
|
||||
Required: false,
|
||||
Placeholder: "{\"group\": \"Wavelet\", \"sound\": \"minuet\", \"icon\": \"https://...\"}",
|
||||
Description: "可选的 JSON 配置,支持 group (分组)、sound (铃声)、icon (自定义图标)",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelDiscord,
|
||||
Name: "Discord 频道",
|
||||
Description: "配置 Discord 频道的 Incoming Webhook 消息推送。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "Webhook 地址",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "https://discord.com/api/webhooks/...",
|
||||
Description: "从 Discord 频道集成设置中复制的 Webhook URL",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelSlack,
|
||||
Name: "Slack 频道",
|
||||
Description: "配置 Slack 频道的 Incoming Webhook 消息推送。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "Webhook 地址",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "https://hooks.slack.com/services/...",
|
||||
Description: "从 Slack 应用配置中复制的 Incoming Webhook URL",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelPushover,
|
||||
Name: "Pushover 推送",
|
||||
Description: "配置 Pushover 即时推送到手机/桌面客户端。",
|
||||
Fields: []PushField{
|
||||
{
|
||||
Key: consts.KeyToken,
|
||||
Label: "应用 Token (App Token)",
|
||||
Type: consts.TypePassword,
|
||||
Required: true,
|
||||
Placeholder: "Pushover 创建应用生成的 API Token / Key",
|
||||
Description: "从 Pushover 控制台创建的 Application API Token",
|
||||
},
|
||||
{
|
||||
Key: consts.KeyURL,
|
||||
Label: "用户 Key (User Key)",
|
||||
Type: consts.TypeText,
|
||||
Required: true,
|
||||
Placeholder: "Pushover 账号主页的 User Key",
|
||||
Description: "Pushover 个人账号的 User Key",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
RegisterPushChannelDefinition(PushDefinition{
|
||||
Type: consts.ChannelEmail,
|
||||
Name: "邮件推送通道",
|
||||
Description: "邮件推送通道直接使用系统全局 SMTP 设置进行发送,无需在此填写服务器配置。",
|
||||
Fields: []PushField{},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user