webapi_event_converter.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package handlers
  2. import "github.com/mk6i/open-oscar-server/server/webapi/types"
  3. // ConvertEventForAMF3 converts a WebAPIEvent to a map suitable for AMF3 encoding,
  4. // ensuring all timestamps are float64 to avoid uint29 overflow issues.
  5. func ConvertEventForAMF3(event types.Event) map[string]interface{} {
  6. result := map[string]interface{}{
  7. "type": string(event.Type),
  8. "seqNum": event.SeqNum,
  9. "timestamp": float64(event.Timestamp), // Convert to float64
  10. }
  11. // Convert event data based on type
  12. switch event.Type {
  13. case types.EventTypeIM:
  14. if imEvent, ok := event.Data.(types.IMEvent); ok {
  15. // Gromit expects 'source' as a user object and 'autoresponse' (lowercase)
  16. eventData := map[string]interface{}{
  17. "source": map[string]interface{}{
  18. "aimId": imEvent.Source.AimID,
  19. "displayId": imEvent.Source.DisplayID,
  20. "userType": imEvent.Source.UserType,
  21. "state": imEvent.Source.State,
  22. },
  23. "message": imEvent.Message,
  24. "timestamp": imEvent.Timestamp, // Already float64
  25. "autoresponse": imEvent.AutoResp,
  26. }
  27. if imEvent.MsgID != "" {
  28. eventData["msgId"] = imEvent.MsgID
  29. }
  30. result["eventData"] = eventData
  31. } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
  32. // Already a map, ensure timestamps are float64
  33. if ts, exists := dataMap["timestamp"]; exists {
  34. if tsInt, ok := ts.(int64); ok {
  35. dataMap["timestamp"] = float64(tsInt)
  36. }
  37. }
  38. result["eventData"] = dataMap
  39. } else {
  40. result["eventData"] = event.Data
  41. }
  42. case types.EventTypeOfflineIM:
  43. if imEvent, ok := event.Data.(types.IMEvent); ok {
  44. result["eventData"] = map[string]interface{}{
  45. "aimId": imEvent.Source.AimID,
  46. "message": imEvent.Message,
  47. "timestamp": float64(imEvent.Timestamp), // Convert to float64
  48. }
  49. } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
  50. // Already a map, ensure timestamps are float64
  51. if ts, exists := dataMap["timestamp"]; exists {
  52. if tsInt, ok := ts.(int64); ok {
  53. dataMap["timestamp"] = float64(tsInt)
  54. }
  55. }
  56. result["eventData"] = dataMap
  57. } else {
  58. result["eventData"] = event.Data
  59. }
  60. case types.EventTypePresence:
  61. if presenceEvent, ok := event.Data.(types.PresenceEvent); ok {
  62. eventData := map[string]interface{}{
  63. "aimId": presenceEvent.AimID,
  64. "state": presenceEvent.State,
  65. "userType": presenceEvent.UserType,
  66. }
  67. // Convert timestamp fields to float64
  68. if presenceEvent.OnlineTime > 0 {
  69. eventData["onlineTime"] = float64(presenceEvent.OnlineTime)
  70. }
  71. result["eventData"] = eventData
  72. } else if dataMap, ok := event.Data.(map[string]interface{}); ok {
  73. // Already a map, ensure timestamps are float64
  74. if ot, exists := dataMap["onlineTime"]; exists {
  75. if otInt, ok := ot.(int64); ok {
  76. dataMap["onlineTime"] = float64(otInt)
  77. }
  78. }
  79. result["eventData"] = dataMap
  80. } else {
  81. result["eventData"] = event.Data
  82. }
  83. case types.EventType("myInfo"):
  84. // MyInfo events often contain timestamps
  85. if dataMap, ok := event.Data.(map[string]interface{}); ok {
  86. // Convert any int64 timestamps to float64
  87. for key, val := range dataMap {
  88. if key == "onlineTime" || key == "memberSince" || key == "awayTime" || key == "statusTime" {
  89. if intVal, ok := val.(int64); ok {
  90. dataMap[key] = float64(intVal)
  91. }
  92. }
  93. }
  94. result["eventData"] = dataMap
  95. } else {
  96. result["eventData"] = event.Data
  97. }
  98. case types.EventTypeBuddyList:
  99. // Buddy list events are already converted to maps in FormatBuddyListEvent
  100. // Just pass through
  101. result["eventData"] = event.Data
  102. case types.EventTypeTyping:
  103. result["eventData"] = event.Data
  104. case types.EventTypeSentIM:
  105. if sentIMEvent, ok := event.Data.(types.SentIMEvent); ok {
  106. // Gromit expects both 'source' (sender) and 'dest' (recipient) for sentIM
  107. // The parseIM function needs source even for outgoing messages
  108. eventData := map[string]interface{}{
  109. "source": map[string]interface{}{
  110. "aimId": sentIMEvent.Sender.AimID,
  111. "displayId": sentIMEvent.Sender.DisplayID,
  112. "userType": sentIMEvent.Sender.UserType,
  113. "state": "online",
  114. },
  115. "dest": map[string]interface{}{
  116. "aimId": sentIMEvent.Dest.AimID,
  117. "displayId": sentIMEvent.Dest.DisplayID,
  118. "userType": sentIMEvent.Dest.UserType,
  119. "state": "online",
  120. },
  121. "message": sentIMEvent.Message,
  122. "timestamp": sentIMEvent.Timestamp, // Already float64
  123. "autoresponse": sentIMEvent.AutoResp,
  124. }
  125. if sentIMEvent.MsgID != "" {
  126. eventData["msgId"] = sentIMEvent.MsgID
  127. }
  128. result["eventData"] = eventData
  129. } else {
  130. result["eventData"] = event.Data
  131. }
  132. default:
  133. // For unknown types, check if data is a map and convert any int64 values
  134. if dataMap, ok := event.Data.(map[string]interface{}); ok {
  135. result["eventData"] = convertTimestampsInMap(dataMap)
  136. } else {
  137. result["eventData"] = event.Data
  138. }
  139. }
  140. return result
  141. }
  142. // convertTimestampsInMap recursively converts int64 values that look like timestamps to float64
  143. func convertTimestampsInMap(data map[string]interface{}) map[string]interface{} {
  144. result := make(map[string]interface{})
  145. for key, val := range data {
  146. // Check if key suggests it's a timestamp
  147. if isTimestampField(key) {
  148. if intVal, ok := val.(int64); ok {
  149. result[key] = float64(intVal)
  150. continue
  151. }
  152. }
  153. // Recursively process nested maps
  154. if nestedMap, ok := val.(map[string]interface{}); ok {
  155. result[key] = convertTimestampsInMap(nestedMap)
  156. } else if nestedSlice, ok := val.([]interface{}); ok {
  157. convertedSlice := make([]interface{}, len(nestedSlice))
  158. for i, item := range nestedSlice {
  159. if itemMap, ok := item.(map[string]interface{}); ok {
  160. convertedSlice[i] = convertTimestampsInMap(itemMap)
  161. } else {
  162. convertedSlice[i] = item
  163. }
  164. }
  165. result[key] = convertedSlice
  166. } else {
  167. result[key] = val
  168. }
  169. }
  170. return result
  171. }
  172. // isTimestampField checks if a field name suggests it contains a timestamp
  173. func isTimestampField(fieldName string) bool {
  174. timestampFields := []string{
  175. "timestamp", "Timestamp",
  176. "onlineTime", "OnlineTime",
  177. "memberSince", "MemberSince",
  178. "awayTime", "AwayTime",
  179. "statusTime", "StatusTime",
  180. "idleTime", "IdleTime",
  181. "loginTime", "LoginTime",
  182. "createdAt", "CreatedAt",
  183. "updatedAt", "UpdatedAt",
  184. }
  185. for _, tf := range timestampFields {
  186. if fieldName == tf {
  187. return true
  188. }
  189. }
  190. return false
  191. }
  192. // ConvertEventsForAMF3 converts a slice of WebAPIEvents for AMF3 encoding
  193. func ConvertEventsForAMF3(events []types.Event) []interface{} {
  194. result := make([]interface{}, len(events))
  195. for i, event := range events {
  196. result[i] = ConvertEventForAMF3(event)
  197. }
  198. return result
  199. }