2026-06-08 14:12:23 +08:00

154 lines
4.3 KiB
Go

package cpnotice
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
// cpApplication mirrors the user-service CP outbox payload without importing user-service internals.
type cpApplication struct {
ApplicationID string
RelationType string
Status string
Requester cpUser
Target cpUser
RoomID string
RoomRegionID int64
Gift cpGift
ExpiresAtMS int64
DecidedAtMS int64
}
type cpUser struct {
UserID int64
DisplayUserID string
Username string
Avatar string
}
type cpGift struct {
GiftID string
GiftName string
GiftIconURL string
GiftAnimationURL string
GiftCount int64
GiftValue int64
BillingReceiptID string
}
func decodeObject(value string) (map[string]any, error) {
var payload map[string]any
if err := json.Unmarshal([]byte(strings.TrimSpace(value)), &payload); err != nil {
return nil, fmt.Errorf("invalid cp payload json: %w", err)
}
if payload == nil {
payload = map[string]any{}
}
return payload, nil
}
func applicationFromObject(obj map[string]any) (cpApplication, error) {
if len(obj) == 0 {
return cpApplication{}, fmt.Errorf("cp application payload is missing")
}
app := cpApplication{
ApplicationID: stringValue(obj, "application_id", "ApplicationID"),
RelationType: stringValue(obj, "relation_type", "RelationType"),
Status: stringValue(obj, "status", "Status"),
Requester: userFromObject(firstObject(obj, "requester", "Requester")),
Target: userFromObject(firstObject(obj, "target", "Target")),
RoomID: stringValue(obj, "room_id", "RoomID"),
RoomRegionID: int64Value(obj, "room_region_id", "RoomRegionID"),
Gift: giftFromObject(firstObject(obj, "gift", "Gift")),
ExpiresAtMS: int64Value(obj, "expires_at_ms", "ExpiresAtMS"),
DecidedAtMS: int64Value(obj, "decided_at_ms", "DecidedAtMS"),
}
if app.ApplicationID == "" || app.RelationType == "" || app.Requester.UserID <= 0 || app.Target.UserID <= 0 {
return cpApplication{}, fmt.Errorf("cp application payload is incomplete")
}
return app, nil
}
func userFromObject(obj map[string]any) cpUser {
return cpUser{
UserID: int64Value(obj, "user_id", "UserID"),
DisplayUserID: stringValue(obj, "display_user_id", "DisplayUserID"),
Username: stringValue(obj, "username", "Username"),
Avatar: stringValue(obj, "avatar", "Avatar"),
}
}
func giftFromObject(obj map[string]any) cpGift {
return cpGift{
GiftID: stringValue(obj, "gift_id", "GiftID"),
GiftName: stringValue(obj, "gift_name", "GiftName"),
GiftIconURL: stringValue(obj, "gift_icon_url", "GiftIconURL"),
GiftAnimationURL: stringValue(obj, "gift_animation_url", "GiftAnimationURL"),
GiftCount: int64Value(obj, "gift_count", "GiftCount"),
GiftValue: int64Value(obj, "gift_value", "GiftValue"),
BillingReceiptID: stringValue(obj, "billing_receipt_id", "BillingReceiptID"),
}
}
func firstObject(obj map[string]any, keys ...string) map[string]any {
for _, key := range keys {
if value, ok := obj[key]; ok {
if typed, ok := value.(map[string]any); ok {
return typed
}
// json.Unmarshal into map can leave nested structs only as maps; this defensive re-marshal keeps tests and
// future typed adapters from breaking the parser if they pass a struct-like object.
body, err := json.Marshal(value)
if err == nil {
var nested map[string]any
if json.Unmarshal(body, &nested) == nil {
return nested
}
}
}
}
return map[string]any{}
}
func stringValue(obj map[string]any, keys ...string) string {
for _, key := range keys {
switch value := obj[key].(type) {
case string:
return strings.TrimSpace(value)
case json.Number:
return value.String()
case float64:
if value == float64(int64(value)) {
return strconv.FormatInt(int64(value), 10)
}
case int64:
return strconv.FormatInt(value, 10)
case int:
return strconv.Itoa(value)
}
}
return ""
}
func int64Value(obj map[string]any, keys ...string) int64 {
for _, key := range keys {
switch value := obj[key].(type) {
case float64:
return int64(value)
case int64:
return value
case int:
return int64(value)
case json.Number:
parsed, _ := value.Int64()
return parsed
case string:
parsed, _ := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
return parsed
}
}
return 0
}