Initialize LineUp app server
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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
|
||||
Reference in New Issue
Block a user