webapi_event_converter.go 6.5 KB

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