// // LNIMMessageData.swift // Lanu // // Created by OneeChan on 2025/12/10. // import Foundation import AutoCodable enum LNIMMessageDataType { case none case official case system case text case image case voice case order case call(String) } enum LNIMMessageCustomType: String, Decodable { case none case official_image_text case playmate_order } @AutoCodable class LNIMCustomMessage: Decodable { var businessID: LNIMMessageCustomType = .none } @AutoCodable class LNIMVoiceCallMessage: Decodable { var actionType: Int = 0 var businessID: Int = 0 var inviteID: String = "" var data: String = "" private var decodedData: LNIMVoiceCallData? var callData: LNIMVoiceCallData? { if let decodedData { return decodedData } guard let data = data.data(using: .utf8) else { return nil } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .useDefaultKeys decodedData = try? decoder.decode(LNIMVoiceCallData.self, from: data) return decodedData } } @AutoCodable class LNIMVoiceCallData: Decodable { var call_end: Int = 0 var data: LNIMVoiceCallExtraData? init() { } } @AutoCodable class LNIMVoiceCallExtraData: Decodable { var excludeFromHistoryMessage: Bool = false var cmd: String = "" } @AutoCodable class LNIMOfficialMessage: Decodable { var title: String = "" var content: String = "" var image: String = "" var link: String = "" } @AutoCodable class LNIMOrderMessage: Decodable { var orderId: String = "" var bizCategoryName: String = "" var categoryIcon: String = "" var price: Double = 0.0 var unit: String = "" var purchaseQty: Int = 0 var status: LNOrderStatus = .created var createTime: Double = 0.0 var star: Double = 0.0 var refundApply: Bool = false var customerRemark: String = "" var buyerUserNo: String = "" } private extension V2TIMElemType { var toDataType: LNIMMessageDataType { switch self { case .ELEM_TYPE_TEXT: .text case .ELEM_TYPE_IMAGE: .image case .ELEM_TYPE_SOUND: .voice case .ELEM_TYPE_CUSTOM: .none default: .none } } } class LNIMMessageData: NSObject { let imMessage: V2TIMMessage private(set) var type: LNIMMessageDataType private(set) var content: String? private(set) var textContent: NSAttributedString? private var customMessage: (any Decodable)? var readReceipt: V2TIMMessageReceipt? init(imMessage: V2TIMMessage) { self.imMessage = imMessage self.type = imMessage.elemType.toDataType super.init() switch imMessage.elemType { case .ELEM_TYPE_TEXT: setupTextContent() case .ELEM_TYPE_IMAGE: setupImageContent() case .ELEM_TYPE_SOUND: setupVoiceContent() case .ELEM_TYPE_CUSTOM: setupCustomContent() default: break } } func decodeCustomMessage() -> T? { if let cached = customMessage as? T { return cached } guard let data = imMessage.customElem?.data else { return nil } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .useDefaultKeys // 处理蛇形命名 let result = try? decoder.decode(T.self, from: data) if let result { customMessage = result } return result } static func buildDateMessage(date: Date) -> LNIMMessageData { let data = LNIMMessageData(imMessage: V2TIMMessage()) data.type = .system data.content = date.timeIntervalSince1970.tencentIMTimeDesc return data } } extension LNIMMessageData { private func setupTextContent() { content = imMessage.textElem?.text textContent = content?.getEmojiString(with: .body_l) } private func setupImageContent() { if imMessage.isSelf, let path = imMessage.imageElem?.path { if FileManager.default.fileExists(atPath: path) { content = path // 使用本地文件路径 return } else if let fixedPath = path.fixedFilePath, FileManager.default.fileExists(atPath: fixedPath) { content = fixedPath // UUID 变化后的本地文件路径 return } } content = imMessage.imageElem?.imageList.first(where: { $0.type == .IMAGE_TYPE_THUMB })?.url } private func setupVoiceContent() { content = imMessage.soundElem?.path } private func setupCustomContent() { guard let data = imMessage.customElem?.data else { return } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .useDefaultKeys // 处理蛇形命名 // 判断是否是通话消息 if let voiceCall = try? decoder.decode(LNIMVoiceCallMessage.self, from: data), voiceCall.actionType != 0, voiceCall.businessID != 0 { type = .call(voiceCall.inviteID) return } // 判断是否是自定义消息 if let result = try? decoder.decode(LNIMCustomMessage.self, from: data) { switch result.businessID { case .playmate_order: type = .order // 订单消息 case .official_image_text: type = .official // 官方消息 case .none: type = .none } return } type = .none } }