| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- package handlers
- import "github.com/mk6i/open-oscar-server/server/webapi/types"
- // ConvertEventForAMF3 converts a WebAPIEvent to a map suitable for AMF3 encoding,
- // ensuring all timestamps are float64 to avoid uint29 overflow issues.
- func ConvertEventForAMF3(event types.Event) map[string]interface{} {
- result := map[string]interface{}{
- "type": string(event.Type),
- "seqNum": event.SeqNum,
- "timestamp": float64(event.Timestamp), // Convert to float64
- }
- // Convert event data based on type
- switch event.Type {
- case types.EventTypeIM:
- if imEvent, ok := event.Data.(types.IMEvent); ok {
- // Gromit expects 'source' as a user object and 'autoresponse' (lowercase)
- eventData := map[string]interface{}{
- "source": map[string]interface{}{
- "aimId": imEvent.Source.AimID,
- "displayId": imEvent.Source.DisplayID,
- "userType": imEvent.Source.UserType,
- "state": imEvent.Source.State,
- },
- "message": imEvent.Message,
- "timestamp": imEvent.Timestamp, // Already float64
- "autoresponse": imEvent.AutoResp,
- }
- if imEvent.MsgID != "" {
- eventData["msgId"] = imEvent.MsgID
- }
- result["eventData"] = eventData
- } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
- // Already a map, ensure timestamps are float64
- if ts, exists := dataMap["timestamp"]; exists {
- if tsInt, ok := ts.(int64); ok {
- dataMap["timestamp"] = float64(tsInt)
- }
- }
- result["eventData"] = dataMap
- } else {
- result["eventData"] = event.Data
- }
- case types.EventTypeOfflineIM:
- if imEvent, ok := event.Data.(types.IMEvent); ok {
- result["eventData"] = map[string]interface{}{
- "aimId": imEvent.Source.AimID,
- "message": imEvent.Message,
- "timestamp": float64(imEvent.Timestamp), // Convert to float64
- }
- } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
- // Already a map, ensure timestamps are float64
- if ts, exists := dataMap["timestamp"]; exists {
- if tsInt, ok := ts.(int64); ok {
- dataMap["timestamp"] = float64(tsInt)
- }
- }
- result["eventData"] = dataMap
- } else {
- result["eventData"] = event.Data
- }
- case types.EventTypePresence:
- if presenceEvent, ok := event.Data.(types.PresenceEvent); ok {
- eventData := map[string]interface{}{
- "aimId": presenceEvent.AimID,
- "state": presenceEvent.State,
- "userType": presenceEvent.UserType,
- }
- // Convert timestamp fields to float64
- if presenceEvent.OnlineTime > 0 {
- eventData["onlineTime"] = float64(presenceEvent.OnlineTime)
- }
- // This branch flattens PresenceEvent through an explicit allowlist, so
- // buddyIcon must be added here or it never reaches an AMF3 client.
- if presenceEvent.BuddyIcon != "" {
- eventData["buddyIcon"] = presenceEvent.BuddyIcon
- }
- result["eventData"] = eventData
- } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
- // Already a map, ensure timestamps are float64
- if ot, exists := dataMap["onlineTime"]; exists {
- if otInt, ok := ot.(int64); ok {
- dataMap["onlineTime"] = float64(otInt)
- }
- }
- result["eventData"] = dataMap
- } else {
- result["eventData"] = event.Data
- }
- case types.EventType("myInfo"):
- // MyInfo events often contain timestamps
- if dataMap, ok := event.Data.(map[string]interface{}); ok {
- // Convert any int64 timestamps to float64
- for key, val := range dataMap {
- if key == "onlineTime" || key == "memberSince" || key == "awayTime" || key == "statusTime" {
- if intVal, ok := val.(int64); ok {
- dataMap[key] = float64(intVal)
- }
- }
- }
- result["eventData"] = dataMap
- } else {
- result["eventData"] = event.Data
- }
- case types.EventTypeBuddyList:
- // Buddy list events are already converted to maps in FormatBuddyListEvent
- // Just pass through
- result["eventData"] = event.Data
- case types.EventTypeTyping:
- result["eventData"] = event.Data
- case types.EventTypeSentIM:
- if sentIMEvent, ok := event.Data.(types.SentIMEvent); ok {
- // Gromit expects both 'source' (sender) and 'dest' (recipient) for sentIM
- // The parseIM function needs source even for outgoing messages
- eventData := map[string]interface{}{
- "source": map[string]interface{}{
- "aimId": sentIMEvent.Sender.AimID,
- "displayId": sentIMEvent.Sender.DisplayID,
- "userType": sentIMEvent.Sender.UserType,
- "state": "online",
- },
- "dest": map[string]interface{}{
- "aimId": sentIMEvent.Dest.AimID,
- "displayId": sentIMEvent.Dest.DisplayID,
- "userType": sentIMEvent.Dest.UserType,
- "state": "online",
- },
- "message": sentIMEvent.Message,
- "timestamp": sentIMEvent.Timestamp, // Already float64
- "autoresponse": sentIMEvent.AutoResp,
- }
- if sentIMEvent.MsgID != "" {
- eventData["msgId"] = sentIMEvent.MsgID
- }
- result["eventData"] = eventData
- } else {
- result["eventData"] = event.Data
- }
- default:
- // For unknown types, check if data is a map and convert any int64 values
- if dataMap, ok := event.Data.(map[string]interface{}); ok {
- result["eventData"] = convertTimestampsInMap(dataMap)
- } else {
- result["eventData"] = event.Data
- }
- }
- return result
- }
- // convertTimestampsInMap recursively converts int64 values that look like timestamps to float64
- func convertTimestampsInMap(data map[string]interface{}) map[string]interface{} {
- result := make(map[string]interface{})
- for key, val := range data {
- // Check if key suggests it's a timestamp
- if isTimestampField(key) {
- if intVal, ok := val.(int64); ok {
- result[key] = float64(intVal)
- continue
- }
- }
- // Recursively process nested maps
- if nestedMap, ok := val.(map[string]interface{}); ok {
- result[key] = convertTimestampsInMap(nestedMap)
- } else if nestedSlice, ok := val.([]interface{}); ok {
- convertedSlice := make([]interface{}, len(nestedSlice))
- for i, item := range nestedSlice {
- if itemMap, ok := item.(map[string]interface{}); ok {
- convertedSlice[i] = convertTimestampsInMap(itemMap)
- } else {
- convertedSlice[i] = item
- }
- }
- result[key] = convertedSlice
- } else {
- result[key] = val
- }
- }
- return result
- }
- // isTimestampField checks if a field name suggests it contains a timestamp
- func isTimestampField(fieldName string) bool {
- timestampFields := []string{
- "timestamp", "Timestamp",
- "onlineTime", "OnlineTime",
- "memberSince", "MemberSince",
- "awayTime", "AwayTime",
- "statusTime", "StatusTime",
- "idleTime", "IdleTime",
- "loginTime", "LoginTime",
- "createdAt", "CreatedAt",
- "updatedAt", "UpdatedAt",
- }
- for _, tf := range timestampFields {
- if fieldName == tf {
- return true
- }
- }
- return false
- }
- // ConvertEventsForAMF3 converts a slice of WebAPIEvents for AMF3 encoding
- func ConvertEventsForAMF3(events []types.Event) []interface{} {
- result := make([]interface{}, len(events))
- for i, event := range events {
- result[i] = ConvertEventForAMF3(event)
- }
- return result
- }
|