LNIMMessageData.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. //
  2. // LNIMMessageData.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/10.
  6. //
  7. import Foundation
  8. import AutoCodable
  9. enum LNIMMessageDataType {
  10. case none
  11. case official
  12. case system
  13. case text
  14. case image
  15. case voice
  16. case order
  17. case call(String)
  18. case autoReply(LNAutoReplyType)
  19. var isUserContent: Bool {
  20. switch self {
  21. case .text, .image, .voice: true
  22. default: false
  23. }
  24. }
  25. }
  26. enum LNIMMessageCustomType: String, Decodable {
  27. case none
  28. case official_image_text
  29. case playmate_order
  30. case playmate_welcome_text
  31. case playmate_welcome_voice
  32. }
  33. @AutoCodable
  34. class LNIMCustomMessage: Decodable {
  35. var businessID: LNIMMessageCustomType = .none
  36. }
  37. extension SignalingActionType: @retroactive Decodable { }
  38. @AutoCodable
  39. class LNIMVoiceCallMessage: Decodable {
  40. var inviter: String = ""
  41. var actionType: SignalingActionType?
  42. var businessID: Int = 0
  43. var inviteID: String = ""
  44. var data: String = ""
  45. private var decodedData: LNIMVoiceCallData?
  46. var callData: LNIMVoiceCallData? {
  47. if let decodedData {
  48. return decodedData
  49. }
  50. guard let data = data.data(using: .utf8) else { return nil }
  51. let decoder = JSONDecoder()
  52. decoder.keyDecodingStrategy = .useDefaultKeys
  53. decodedData = try? decoder.decode(LNIMVoiceCallData.self, from: data)
  54. return decodedData
  55. }
  56. var contentDesc: String {
  57. guard let callData else { return "" }
  58. return if actionType == SignalingActionType.invite,
  59. let data = callData.data, data.cmd == "hangup" {
  60. .init(key: "C00001") + " " + callData.call_end.timeCountDisplay
  61. } else if actionType == SignalingActionType.cancel_Invite {
  62. if inviter.isMyUid {
  63. .init(key: "A00152")
  64. } else {
  65. .init(key: "A00142")
  66. }
  67. } else if actionType == SignalingActionType.reject_Invite {
  68. if callData.data?.cmd == "line_busy" {
  69. if inviter.isMyUid {
  70. .init(key: "C00004")
  71. } else {
  72. .init(key: "C00011")
  73. }
  74. } else {
  75. if inviter.isMyUid {
  76. .init(key: "C00005")
  77. } else {
  78. .init(key: "C00009")
  79. }
  80. }
  81. } else if actionType == SignalingActionType.invite_Timeout {
  82. if inviter.isMyUid {
  83. .init(key: "C00010")
  84. } else {
  85. .init(key: "C00002")
  86. }
  87. } else {
  88. ""
  89. }
  90. }
  91. }
  92. @AutoCodable
  93. class LNIMVoiceCallData: Decodable {
  94. var call_end: Int = 0
  95. var data: LNIMVoiceCallExtraData?
  96. init() { }
  97. }
  98. @AutoCodable
  99. class LNIMVoiceCallExtraData: Decodable {
  100. var excludeFromHistoryMessage: Bool = false
  101. var cmd: String = ""
  102. }
  103. @AutoCodable
  104. class LNIMOfficialMessage: Decodable {
  105. var title: String = ""
  106. var content: String = ""
  107. var image: String = ""
  108. var link: String = ""
  109. }
  110. @AutoCodable
  111. class LNIMOrderMessage: Decodable, LNOrderProtocol {
  112. var orderId: String = ""
  113. var bizCategoryName: String = ""
  114. var categoryIcon: String = ""
  115. var price: Double = 0.0
  116. var unit: String = ""
  117. var purchaseQty: Int = 0
  118. var status: LNOrderStatus = .created
  119. var createTime: Double = 0.0
  120. var star: Double = 0.0
  121. var refundApply: Bool = false
  122. var customerRemark: String = ""
  123. var buyerUserNo: String = ""
  124. }
  125. @AutoCodable
  126. class LNAutoReplyTextMessage: Decodable {
  127. var textContent: String = ""
  128. }
  129. @AutoCodable
  130. class LNAutoReplyVoiceMessage: Decodable {
  131. var voiceUrl: String = ""
  132. var voiceDuration: Int = 0
  133. }
  134. private extension V2TIMElemType {
  135. var toDataType: LNIMMessageDataType {
  136. switch self {
  137. case .ELEM_TYPE_TEXT: .text
  138. case .ELEM_TYPE_IMAGE: .image
  139. case .ELEM_TYPE_SOUND: .voice
  140. case .ELEM_TYPE_CUSTOM: .none
  141. default: .none
  142. }
  143. }
  144. }
  145. class LNIMMessageData: NSObject {
  146. let imMessage: V2TIMMessage
  147. private(set) var type: LNIMMessageDataType
  148. private(set) var content: String?
  149. private(set) var textContent: NSAttributedString?
  150. private(set) var voiceDuration: Int = 0
  151. private var customMessage: (any Decodable)?
  152. var readReceipt: V2TIMMessageReceipt?
  153. var isSelf: Bool
  154. init(imMessage: V2TIMMessage) {
  155. self.imMessage = imMessage
  156. self.type = imMessage.elemType.toDataType
  157. self.isSelf = imMessage.isSelf
  158. super.init()
  159. switch imMessage.elemType {
  160. case .ELEM_TYPE_TEXT: setupTextContent()
  161. case .ELEM_TYPE_IMAGE: setupImageContent()
  162. case .ELEM_TYPE_SOUND: setupVoiceContent()
  163. case .ELEM_TYPE_CUSTOM: setupCustomContent()
  164. default:
  165. break
  166. }
  167. }
  168. func decodeCustomMessage<T: Decodable>() -> T? {
  169. if let cached = customMessage as? T {
  170. return cached
  171. }
  172. guard let data = imMessage.customElem?.data else { return nil }
  173. let decoder = JSONDecoder()
  174. decoder.keyDecodingStrategy = .useDefaultKeys
  175. let result = try? decoder.decode(T.self, from: data)
  176. if let result {
  177. customMessage = result
  178. }
  179. return result
  180. }
  181. static func buildDateMessage(date: Date) -> LNIMMessageData {
  182. let data = LNIMMessageData(imMessage: V2TIMMessage())
  183. data.type = .system
  184. data.content = date.timeIntervalSince1970.tencentIMTimeDesc
  185. return data
  186. }
  187. }
  188. extension LNIMMessageData {
  189. private func setupTextContent() {
  190. content = imMessage.textElem?.text
  191. textContent = content?.getEmojiString(with: .body_l)
  192. }
  193. private func setupImageContent() {
  194. if imMessage.isSelf, let path = imMessage.imageElem?.path {
  195. if FileManager.default.fileExists(atPath: path) {
  196. content = path // 使用本地文件路径
  197. return
  198. } else if let fixedPath = path.fixedFilePath,
  199. FileManager.default.fileExists(atPath: fixedPath) {
  200. content = fixedPath // UUID 变化后的本地文件路径
  201. return
  202. }
  203. }
  204. content = imMessage.imageElem?.imageList.first(where: { $0.type == .IMAGE_TYPE_THUMB })?.url
  205. }
  206. private func setupVoiceContent() {
  207. content = imMessage.soundElem?.path
  208. voiceDuration = Int(imMessage.soundElem?.duration ?? 0)
  209. }
  210. private func setupCustomContent() {
  211. guard let data = imMessage.customElem?.data else { return }
  212. let decoder = JSONDecoder()
  213. decoder.keyDecodingStrategy = .useDefaultKeys // 处理蛇形命名
  214. // 判断是否是通话消息
  215. if let voiceCall = try? decoder.decode(LNIMVoiceCallMessage.self, from: data),
  216. voiceCall.actionType != nil, voiceCall.businessID != 0 {
  217. type = .call(voiceCall.inviteID)
  218. return
  219. }
  220. // 判断是否是自定义消息
  221. if let result = try? decoder.decode(LNIMCustomMessage.self, from: data) {
  222. switch result.businessID {
  223. case .playmate_order:
  224. type = .order // 订单消息
  225. case .official_image_text:
  226. type = .official // 官方消息
  227. case .playmate_welcome_text:
  228. type = .autoReply(.text)
  229. if let message: LNAutoReplyTextMessage = decodeCustomMessage() {
  230. textContent = NSMutableAttributedString(string: message.textContent)
  231. }
  232. case .playmate_welcome_voice:
  233. type = .autoReply(.voice)
  234. if let message: LNAutoReplyVoiceMessage = decodeCustomMessage() {
  235. content = message.voiceUrl
  236. voiceDuration = message.voiceDuration
  237. }
  238. case .none: type = .none
  239. }
  240. return
  241. }
  242. type = .none
  243. }
  244. }