152 lines
3.9 KiB
Go
152 lines
3.9 KiB
Go
package modules
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/lineup/app-server/internal"
|
|
)
|
|
|
|
// WebhookModule receives WuKongIM 3.0 HTTP webhook callbacks.
|
|
type WebhookModule struct {
|
|
hub *internal.Hub
|
|
}
|
|
|
|
// NewWebhookModule creates a new WebhookModule.
|
|
func NewWebhookModule(hub *internal.Hub) *WebhookModule {
|
|
return &WebhookModule{hub: hub}
|
|
}
|
|
|
|
// RegisterRoutes registers webhook routes.
|
|
func (m *WebhookModule) RegisterRoutes(r *gin.Engine) {
|
|
r.POST("/v1/webhook", m.handleWebhook)
|
|
}
|
|
|
|
// wkMsgResp mirrors WuKongIM 3.0's msg.notify message format.
|
|
type wkMsgResp struct {
|
|
Header wkMsgHeader `json:"header"`
|
|
Setting uint8 `json:"setting"`
|
|
MessageID uint64 `json:"message_id"`
|
|
MessageSeq uint64 `json:"message_seq"`
|
|
FromUID string `json:"from_uid"`
|
|
ChannelID string `json:"channel_id"`
|
|
ChannelType uint8 `json:"channel_type"`
|
|
Timestamp int32 `json:"timestamp"`
|
|
Payload string `json:"payload"` // base64-encoded
|
|
}
|
|
|
|
type wkMsgHeader struct {
|
|
NoPersist int `json:"no_persist"`
|
|
RedDot int `json:"red_dot"`
|
|
SyncOnce int `json:"sync_once"`
|
|
}
|
|
|
|
// wkOfflineResp mirrors WuKongIM 3.0's msg.offline format.
|
|
type wkOfflineResp struct {
|
|
wkMsgResp
|
|
ToUIDs []string `json:"to_uids"`
|
|
Compress string `json:"compress"`
|
|
CompressToUIDs string `json:"compress_to_uids"`
|
|
SourceID int64 `json:"source_id"`
|
|
}
|
|
|
|
func (m *WebhookModule) handleWebhook(c *gin.Context) {
|
|
event := c.Query("event")
|
|
body, err := io.ReadAll(c.Request.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"})
|
|
return
|
|
}
|
|
|
|
switch event {
|
|
case "msg.notify":
|
|
m.handleMsgNotify(body)
|
|
case "msg.offline":
|
|
m.handleMsgOffline(body)
|
|
case "user.onlinestatus":
|
|
m.handleOnlineStatus(body)
|
|
default:
|
|
// Unknown event, still return 200 per WK webhook contract
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
|
}
|
|
|
|
func (m *WebhookModule) handleMsgNotify(data []byte) {
|
|
var msgs []wkMsgResp
|
|
if err := json.Unmarshal(data, &msgs); err != nil {
|
|
return
|
|
}
|
|
for _, msg := range msgs {
|
|
m.pushToClient(msg.ChannelID, msg)
|
|
}
|
|
}
|
|
|
|
func (m *WebhookModule) handleMsgOffline(data []byte) {
|
|
var msg wkOfflineResp
|
|
if err := json.Unmarshal(data, &msg); err != nil {
|
|
return
|
|
}
|
|
// Push to each offline recipient
|
|
for _, uid := range msg.ToUIDs {
|
|
m.pushToClient(uid, msg.wkMsgResp)
|
|
}
|
|
// Handle gzip-compressed UIDs
|
|
if msg.Compress == "gzip" && len(msg.CompressToUIDs) > 0 {
|
|
decoded, err := base64.StdEncoding.DecodeString(msg.CompressToUIDs)
|
|
if err == nil {
|
|
var uids []string
|
|
if json.Unmarshal(decoded, &uids) == nil {
|
|
for _, uid := range uids {
|
|
m.pushToClient(uid, msg.wkMsgResp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *WebhookModule) handleOnlineStatus(data []byte) {
|
|
// Online status format: ["uid-flag-status", ...]
|
|
var statuses []string
|
|
if err := json.Unmarshal(data, &statuses); err != nil {
|
|
return
|
|
}
|
|
// For now, just log. Could be used to notify clients.
|
|
_ = statuses
|
|
}
|
|
|
|
// pushToClient decodes the payload and pushes to the connected client.
|
|
func (m *WebhookModule) pushToClient(uid string, msg wkMsgResp) {
|
|
if m.hub == nil {
|
|
return
|
|
}
|
|
decoded, _ := base64.StdEncoding.DecodeString(msg.Payload)
|
|
pushMsg := map[string]interface{}{
|
|
"event": "message",
|
|
"message_id": msg.MessageID,
|
|
"message_seq": msg.MessageSeq,
|
|
"from_uid": msg.FromUID,
|
|
"channel_id": msg.ChannelID,
|
|
"channel_type": msg.ChannelType,
|
|
"payload": string(decoded),
|
|
"timestamp": msg.Timestamp,
|
|
}
|
|
pushData, _ := json.Marshal(pushMsg)
|
|
if err := m.hub.PushToClient(uid, pushData); err != nil {
|
|
// Client not connected, that's OK
|
|
_ = err
|
|
}
|
|
// Also push to the other participant if it's a person channel
|
|
// This ensures both sides get the message
|
|
if msg.ChannelType == 1 && msg.FromUID != uid {
|
|
_ = m.hub.PushToClient(msg.FromUID, pushData)
|
|
}
|
|
}
|
|
|
|
// Ensure fmt is used (for potential future logging)
|
|
var _ = fmt.Sprintf
|