| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- //
- // 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)
- case autoReply(LNAutoReplyType)
- }
- enum LNIMMessageCustomType: String, Decodable {
- case none
- case official_image_text
- case playmate_order
- case playmate_welcome_text
- case playmate_welcome_voice
- }
- @AutoCodable
- class LNIMCustomMessage: Decodable {
- var businessID: LNIMMessageCustomType = .none
- }
- extension SignalingActionType: @retroactive Decodable { }
- @AutoCodable
- class LNIMVoiceCallMessage: Decodable {
- var inviter: String = ""
- var actionType: SignalingActionType?
- 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
- }
-
- var contentDesc: String {
- guard let callData else { return "" }
-
- return if actionType == SignalingActionType.invite,
- let data = callData.data, data.cmd == "hangup" {
- .init(key: "C00001") + " " + callData.call_end.timeCountDisplay
- } else if actionType == SignalingActionType.cancel_Invite {
- if inviter.isMyUid {
- .init(key: "A00152")
- } else {
- .init(key: "A00142")
- }
- } else if actionType == SignalingActionType.reject_Invite {
- if callData.data?.cmd == "line_busy" {
- if inviter.isMyUid {
- .init(key: "C00004")
- } else {
- .init(key: "C00011")
- }
- } else {
- if inviter.isMyUid {
- .init(key: "C00005")
- } else {
- .init(key: "C00009")
- }
- }
- } else if actionType == SignalingActionType.invite_Timeout {
- if inviter.isMyUid {
- .init(key: "C00010")
- } else {
- .init(key: "C00002")
- }
- } else {
- ""
- }
- }
- }
- @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, LNOrderProtocol {
- 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 = ""
- }
- @AutoCodable
- class LNAutoReplyTextMessage: Decodable {
- var textContent: String = ""
- }
- @AutoCodable
- class LNAutoReplyVoiceMessage: Decodable {
- var voiceUrl: String = ""
- var voiceDuration: Int = 0
- }
- 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(set) var voiceDuration: Int = 0
-
- private var customMessage: (any Decodable)?
-
- var readReceipt: V2TIMMessageReceipt?
- var isSelf: Bool
-
- init(imMessage: V2TIMMessage) {
- self.imMessage = imMessage
- self.type = imMessage.elemType.toDataType
- self.isSelf = imMessage.isSelf
- 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: Decodable>() -> 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
- voiceDuration = Int(imMessage.soundElem?.duration ?? 0)
- }
-
- 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 != nil, 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 .playmate_welcome_text:
- type = .autoReply(.text)
- if let message: LNAutoReplyTextMessage = decodeCustomMessage() {
- textContent = NSMutableAttributedString(string: message.textContent)
- }
- case .playmate_welcome_voice:
- type = .autoReply(.voice)
- if let message: LNAutoReplyVoiceMessage = decodeCustomMessage() {
- content = message.voiceUrl
- voiceDuration = message.voiceDuration
- }
- case .none: type = .none
- }
- return
- }
- type = .none
- }
- }
|