- 实现了聊天消息发送功能,支持文本、图片、视频、音频、文件等多种消息类型 - 集成了文件上传下载功能,支持多媒体文件的传输和管理 - 添加了群组管理功能,包括创建群组、成员管理、权限控制等操作 - 实现了好友系统,支持好友添加、删除、分组等功能 - 集成了黑名单管理,提供用户屏蔽和解除屏蔽功能 - 添加了会话管理功能,支持对话列表、未读消息统计等 - 实现了历史消息查询和搜索功能 - 添加了实时连接状态管理和自动重连机制
1287 行
46 KiB
Swift
1287 行
46 KiB
Swift
import Foundation
|
|
|
|
public protocol ConversationDelegate: AnyObject {
|
|
func conversationsDidChange(_ conversations: [ConversationData])
|
|
}
|
|
|
|
@MainActor
|
|
public final class ImSDK {
|
|
|
|
public static let shared = ImSDK()
|
|
private var client: ImClient?
|
|
private weak var delegate: ImEventDelegate?
|
|
private weak var conversationDelegate: ConversationDelegate?
|
|
private var currentUserId: String?
|
|
private var _conversations: [ConversationData] = []
|
|
|
|
public private(set) var connectionState: ImConnectionState = .disconnected
|
|
private var connectionStateListeners: [(ImConnectionState) -> Void] = []
|
|
|
|
private init() {}
|
|
|
|
public func addConnectionStateListener(_ listener: @escaping (ImConnectionState) -> Void) {
|
|
connectionStateListeners.append(listener)
|
|
}
|
|
|
|
private func updateConnectionState(_ state: ImConnectionState) {
|
|
connectionState = state
|
|
for listener in connectionStateListeners {
|
|
listener(state)
|
|
}
|
|
}
|
|
|
|
public func login(_ userId: String, _ userSig: String) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
currentUserId = userId
|
|
XuqmSDK.shared.tokenStore?.save(userSig)
|
|
client?.disconnect()
|
|
client = ImClient(token: userSig, appId: config.appId)
|
|
client?.setCurrentUserId(userId)
|
|
client?.delegate = self
|
|
updateConnectionState(.connecting)
|
|
client?.connect()
|
|
}
|
|
|
|
public func setDelegate(_ delegate: ImEventDelegate) {
|
|
self.delegate = delegate
|
|
client?.delegate = self
|
|
}
|
|
|
|
public func setConversationDelegate(_ delegate: ConversationDelegate) {
|
|
self.conversationDelegate = delegate
|
|
}
|
|
|
|
public func sendMessage(toId: String, chatType: ChatType, msgType: MsgType, content: String) -> ImMessage {
|
|
client?.sendMessage(toId: toId, chatType: chatType, msgType: msgType, content: content)
|
|
?? fallbackFailedMessage(toId: toId, chatType: chatType, msgType: msgType, content: content, mentionedUserIds: nil)
|
|
}
|
|
|
|
public func sendTextMessage(toId: String, chatType: ChatType, content: String) -> ImMessage {
|
|
sendMessage(toId: toId, chatType: chatType, msgType: .text, content: content)
|
|
}
|
|
|
|
public func sendImageMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
fileURL: URL,
|
|
width: Int? = nil,
|
|
height: Int? = nil
|
|
) async throws -> ImMessage {
|
|
let result = try await FileSDK.shared.upload(fileURL: fileURL)
|
|
return sendImageMessageWithURL(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
url: result.url,
|
|
thumbnailUrl: result.thumbnailUrl,
|
|
width: width,
|
|
height: height,
|
|
size: result.size
|
|
)
|
|
}
|
|
|
|
private func sendImageMessageWithURL(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
url: String,
|
|
thumbnailUrl: String? = nil,
|
|
width: Int? = nil,
|
|
height: Int? = nil,
|
|
size: Int64? = nil
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .image,
|
|
content: jsonString(from: [
|
|
"url": url,
|
|
"thumbnailUrl": thumbnailUrl as Any,
|
|
"width": width as Any,
|
|
"height": height as Any,
|
|
"size": size as Any,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendVideoMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
fileURL: URL,
|
|
duration: Int64? = nil
|
|
) async throws -> ImMessage {
|
|
let result = try await FileSDK.shared.upload(fileURL: fileURL)
|
|
return sendVideoMessageWithURL(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
url: result.url,
|
|
thumbnailUrl: result.thumbnailUrl,
|
|
duration: duration,
|
|
size: result.size
|
|
)
|
|
}
|
|
|
|
private func sendVideoMessageWithURL(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
url: String,
|
|
thumbnailUrl: String? = nil,
|
|
duration: Int64? = nil,
|
|
size: Int64? = nil
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .video,
|
|
content: jsonString(from: [
|
|
"url": url,
|
|
"thumbnailUrl": thumbnailUrl as Any,
|
|
"duration": duration as Any,
|
|
"size": size as Any,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendAudioMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
fileURL: URL,
|
|
duration: Int64? = nil
|
|
) async throws -> ImMessage {
|
|
let result = try await FileSDK.shared.upload(fileURL: fileURL)
|
|
return sendAudioMessageWithURL(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
url: result.url,
|
|
duration: duration,
|
|
size: result.size
|
|
)
|
|
}
|
|
|
|
private func sendAudioMessageWithURL(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
url: String,
|
|
duration: Int64? = nil,
|
|
size: Int64? = nil
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .audio,
|
|
content: jsonString(from: [
|
|
"url": url,
|
|
"duration": duration as Any,
|
|
"size": size as Any,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendFileMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
fileURL: URL
|
|
) async throws -> ImMessage {
|
|
let result = try await FileSDK.shared.upload(fileURL: fileURL)
|
|
return sendFileMessageWithURL(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
url: result.url,
|
|
name: result.originalName ?? fileURL.lastPathComponent,
|
|
size: result.size
|
|
)
|
|
}
|
|
|
|
private func sendFileMessageWithURL(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
url: String,
|
|
name: String,
|
|
size: Int64
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .file,
|
|
content: jsonString(from: [
|
|
"url": url,
|
|
"name": name,
|
|
"size": size,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendLocationMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
lat: Double,
|
|
lng: Double,
|
|
title: String? = nil,
|
|
address: String? = nil
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .location,
|
|
content: jsonString(from: [
|
|
"lat": lat,
|
|
"lng": lng,
|
|
"title": title as Any,
|
|
"address": address as Any,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendCustomMessage(toId: String, chatType: ChatType, data: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .custom,
|
|
content: jsonString(from: ["data": data])
|
|
)
|
|
}
|
|
|
|
public func sendRichTextMessage(toId: String, chatType: ChatType, html: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .richText,
|
|
content: jsonString(from: ["html": html])
|
|
)
|
|
}
|
|
|
|
public func sendNotifyMessage(toId: String, chatType: ChatType, title: String, content: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .notify,
|
|
content: jsonString(from: ["title": title, "content": content])
|
|
)
|
|
}
|
|
|
|
public func sendQuoteMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
quotedMsgId: String,
|
|
quotedContent: String,
|
|
text: String
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .quote,
|
|
content: jsonString(from: [
|
|
"quotedMsgId": quotedMsgId,
|
|
"quotedContent": quotedContent,
|
|
"text": text,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendMergeMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
title: String,
|
|
msgList: [String]
|
|
) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .merge,
|
|
content: jsonString(from: [
|
|
"title": title,
|
|
"msgList": msgList,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendForwardMessage(toId: String, chatType: ChatType, originalSender: String, originalContent: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .forward,
|
|
content: jsonString(from: [
|
|
"originalSender": originalSender,
|
|
"originalContent": originalContent,
|
|
])
|
|
)
|
|
}
|
|
|
|
public func sendCallAudioMessage(toId: String, chatType: ChatType, action: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .callAudio,
|
|
content: jsonString(from: ["action": action])
|
|
)
|
|
}
|
|
|
|
public func sendCallVideoMessage(toId: String, chatType: ChatType, action: String) -> ImMessage {
|
|
sendMessage(
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: .callVideo,
|
|
content: jsonString(from: ["action": action])
|
|
)
|
|
}
|
|
|
|
public func revokeMessage(messageId: String) async throws -> ImMessage {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/messages/\(messageId)/revoke",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func editMessage(messageId: String, content: String) async throws -> ImMessage {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/messages/\(messageId)",
|
|
method: "PUT",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: EditMessageRequest(content: content)
|
|
)
|
|
}
|
|
|
|
public func subscribeGroup(_ groupId: String) {
|
|
client?.subscribeGroup(groupId)
|
|
}
|
|
|
|
public func unsubscribeGroup(_ groupId: String) {
|
|
client?.unsubscribeGroup(groupId)
|
|
}
|
|
|
|
private func fallbackFailedMessage(
|
|
toId: String,
|
|
chatType: ChatType,
|
|
msgType: MsgType,
|
|
content: String,
|
|
mentionedUserIds: String?
|
|
) -> ImMessage {
|
|
ImMessage(
|
|
id: UUID().uuidString,
|
|
appId: XuqmSDK.shared.requireConfig().appId,
|
|
fromUserId: currentUserId ?? "",
|
|
fromId: currentUserId,
|
|
toId: toId,
|
|
chatType: chatType,
|
|
msgType: msgType,
|
|
content: content,
|
|
status: .failed,
|
|
mentionedUserIds: mentionedUserIds,
|
|
groupReadCount: nil,
|
|
revoked: false,
|
|
createdAt: Int64(Date().timeIntervalSince1970 * 1000),
|
|
editedAt: nil
|
|
)
|
|
}
|
|
|
|
public func fetchHistory(
|
|
toId: String,
|
|
page: Int = 0,
|
|
size: Int = 20,
|
|
msgType: MsgType? = nil,
|
|
keyword: String? = nil,
|
|
startTime: Date? = nil,
|
|
endTime: Date? = nil
|
|
) async throws -> [ImMessage] {
|
|
try await fetchHistoryInternal(
|
|
toId: toId,
|
|
page: page,
|
|
size: size,
|
|
msgType: msgType,
|
|
keyword: keyword,
|
|
startTime: startTime,
|
|
endTime: endTime
|
|
)
|
|
}
|
|
|
|
public func fetchGroupHistory(
|
|
groupId: String,
|
|
page: Int = 0,
|
|
size: Int = 20,
|
|
msgType: MsgType? = nil,
|
|
keyword: String? = nil,
|
|
startTime: Date? = nil,
|
|
endTime: Date? = nil
|
|
) async throws -> [ImMessage] {
|
|
try await fetchHistoryInternal(
|
|
groupId: groupId,
|
|
page: page,
|
|
size: size,
|
|
msgType: msgType,
|
|
keyword: keyword,
|
|
startTime: startTime,
|
|
endTime: endTime,
|
|
isGroup: true
|
|
)
|
|
}
|
|
|
|
public func locateHistoryPage(
|
|
toId: String,
|
|
messageId: String,
|
|
pageSize: Int = 20,
|
|
maxPages: Int = 20
|
|
) async throws -> [ImMessage]? {
|
|
try await locatePage(
|
|
messageId: messageId,
|
|
maxPages: maxPages,
|
|
pageSize: pageSize,
|
|
loadPage: { page in
|
|
try await self.fetchHistory(toId: toId, page: page, size: pageSize)
|
|
}
|
|
)
|
|
}
|
|
|
|
public func locateGroupHistoryPage(
|
|
groupId: String,
|
|
messageId: String,
|
|
pageSize: Int = 20,
|
|
maxPages: Int = 20
|
|
) async throws -> [ImMessage]? {
|
|
try await locatePage(
|
|
messageId: messageId,
|
|
maxPages: maxPages,
|
|
pageSize: pageSize,
|
|
loadPage: { page in
|
|
try await self.fetchGroupHistory(groupId: groupId, page: page, size: pageSize)
|
|
}
|
|
)
|
|
}
|
|
|
|
private func locatePage(
|
|
messageId: String,
|
|
maxPages: Int,
|
|
pageSize: Int,
|
|
loadPage: @escaping (Int) async throws -> [ImMessage]
|
|
) async throws -> [ImMessage]? {
|
|
let pageCount = max(maxPages, 1)
|
|
for page in 0..<pageCount {
|
|
let messages = try await loadPage(page)
|
|
if messages.contains(where: { $0.id == messageId }) {
|
|
return messages
|
|
}
|
|
if messages.count < pageSize {
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func listGroups() async throws -> [ImGroup] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let groups: [ImGroup] = try await ApiClient.shared.request(
|
|
path: "/api/im/groups",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
return groups
|
|
}
|
|
|
|
public func listPublicGroups(keyword: String? = nil) async throws -> [ImGroup] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
|
if let keyword { items.append(URLQueryItem(name: "keyword", value: keyword)) }
|
|
let groups: [ImGroup] = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/public",
|
|
queryItems: items
|
|
)
|
|
return groups
|
|
}
|
|
|
|
public func searchUsers(keyword: String, size: Int = 20) async throws -> [UserProfile] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/admin/users/search",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "keyword", value: keyword),
|
|
URLQueryItem(name: "size", value: String(size)),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func searchGroups(keyword: String, size: Int = 20) async throws -> [ImGroup] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/admin/groups/search",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "keyword", value: keyword),
|
|
URLQueryItem(name: "size", value: String(size)),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func searchMessages(
|
|
keyword: String? = nil,
|
|
chatType: String? = nil,
|
|
msgType: String? = nil,
|
|
startTime: Date? = nil,
|
|
endTime: Date? = nil,
|
|
page: Int = 0,
|
|
size: Int = 20
|
|
) async throws -> PageResult<ImMessage> {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items: [URLQueryItem] = [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "size", value: String(size)),
|
|
]
|
|
if let keyword, !keyword.isEmpty { items.append(URLQueryItem(name: "keyword", value: keyword)) }
|
|
if let chatType, !chatType.isEmpty { items.append(URLQueryItem(name: "chatType", value: chatType)) }
|
|
if let msgType, !msgType.isEmpty { items.append(URLQueryItem(name: "msgType", value: msgType)) }
|
|
if let startTime { items.append(URLQueryItem(name: "startTime", value: isoLocalDateTime(startTime))) }
|
|
if let endTime { items.append(URLQueryItem(name: "endTime", value: isoLocalDateTime(endTime))) }
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/admin/messages/search",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func createGroup(name: String, memberIds: [String], groupType: String = "WORK") async throws -> ImGroup {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: ImCreateGroupRequest(name: name, memberIds: memberIds, groupType: groupType)
|
|
)
|
|
}
|
|
|
|
public func getGroupInfo(groupId: String) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(path: "/api/im/groups/\(groupId)")
|
|
}
|
|
|
|
public func listGroupMembers(groupId: String) async throws -> [UserProfile] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func searchGroupMembers(groupId: String, keyword: String, size: Int = 20) async throws -> [UserProfile] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/search",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "keyword", value: keyword),
|
|
URLQueryItem(name: "size", value: String(size))
|
|
]
|
|
)
|
|
}
|
|
|
|
public func updateGroupInfo(groupId: String, name: String? = nil, announcement: String? = nil) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)",
|
|
method: "PUT",
|
|
body: ImUpdateGroupRequest(name: name, announcement: announcement)
|
|
)
|
|
}
|
|
|
|
public func addGroupMember(groupId: String, userId: String) async throws {
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members",
|
|
method: "POST",
|
|
body: ImMemberRequest(userId: userId)
|
|
)
|
|
}
|
|
|
|
public func removeGroupMember(groupId: String, targetUserId: String) async throws {
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/\(targetUserId)",
|
|
method: "DELETE"
|
|
)
|
|
}
|
|
|
|
public func leaveGroup(groupId: String) async throws {
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/me",
|
|
method: "DELETE"
|
|
)
|
|
}
|
|
|
|
public func setGroupRole(groupId: String, userId: String, role: String) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/roles",
|
|
method: "POST",
|
|
body: ImSetRoleRequest(userId: userId, role: role)
|
|
)
|
|
}
|
|
|
|
public func muteGroupMember(groupId: String, userId: String, minutes: Int64) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/mute",
|
|
method: "POST",
|
|
body: ImMuteMemberRequest(userId: userId, minutes: minutes)
|
|
)
|
|
}
|
|
|
|
public func transferGroupOwner(groupId: String, newOwnerId: String) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/owner",
|
|
method: "POST",
|
|
body: ImTransferOwnerRequest(newOwnerId: newOwnerId)
|
|
)
|
|
}
|
|
|
|
public func updateGroupAttributes(groupId: String, attributes: [String: ImAttributeValue]) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/attributes",
|
|
method: "PUT",
|
|
body: attributes
|
|
)
|
|
}
|
|
|
|
public func removeGroupAttributes(groupId: String, keys: [String]) async throws -> ImGroup {
|
|
try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/attributes/delete",
|
|
method: "POST",
|
|
body: ImAttributeKeysRequest(keys: keys)
|
|
)
|
|
}
|
|
|
|
public func dismissGroup(groupId: String) async throws {
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)",
|
|
method: "DELETE"
|
|
)
|
|
}
|
|
|
|
public func sendGroupJoinRequest(groupId: String, remark: String? = nil) async throws -> GroupJoinRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
|
if let remark { items.append(URLQueryItem(name: "remark", value: remark)) }
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests",
|
|
method: "POST",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func listGroupJoinRequests(groupId: String) async throws -> [GroupJoinRequest] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func acceptGroupJoinRequest(groupId: String, requestId: String) async throws -> GroupJoinRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests/\(requestId)/accept",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func rejectGroupJoinRequest(groupId: String, requestId: String) async throws -> GroupJoinRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests/\(requestId)/reject",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func listFriends() async throws -> [String] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friends",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func addFriend(friendId: String) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends",
|
|
method: "POST",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "friendId", value: friendId),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func removeAllFriends() async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends",
|
|
method: "DELETE",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func removeFriend(friendId: String) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends/\(friendId)",
|
|
method: "DELETE",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func setFriendGroup(friendId: String, groupName: String? = nil) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
|
if let groupName {
|
|
items.append(URLQueryItem(name: "groupName", value: groupName))
|
|
}
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends/\(friendId)/group",
|
|
method: "PUT",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func listFriendGroups() async throws -> [String] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friends/groups",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func listFriendsByGroup(_ groupName: String) async throws -> [String] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friends/groups/\(groupName)",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func listFriendRequests(direction: String = "incoming") async throws -> [FriendRequest] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "direction", value: direction),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func sendFriendRequest(toUserId: String, remark: String? = nil) async throws -> FriendRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "toUserId", value: toUserId),
|
|
]
|
|
if let remark { items.append(URLQueryItem(name: "remark", value: remark)) }
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests",
|
|
method: "POST",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func acceptFriendRequest(requestId: String) async throws -> FriendRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests/\(requestId)/accept",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func rejectFriendRequest(requestId: String) async throws -> FriendRequest {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests/\(requestId)/reject",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func listBlacklist() async throws -> [BlacklistEntry] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/blacklist",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func addToBlacklist(blockedUserId: String) async throws -> BlacklistEntry {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/blacklist",
|
|
method: "POST",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "blockedUserId", value: blockedUserId),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func removeFromBlacklist(blockedUserId: String) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/blacklist",
|
|
method: "DELETE",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "blockedUserId", value: blockedUserId),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func checkBlacklist(targetUserId: String) async throws -> BlacklistCheckResult {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/blacklist/check",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "targetUserId", value: targetUserId),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func getProfile(userId: String) async throws -> UserProfile {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/accounts/\(userId)",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func updateProfile(
|
|
userId: String,
|
|
nickname: String? = nil,
|
|
avatar: String? = nil,
|
|
gender: String? = nil
|
|
) async throws -> UserProfile {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [URLQueryItem(name: "appId", value: config.appId)]
|
|
if let nickname { items.append(URLQueryItem(name: "nickname", value: nickname)) }
|
|
if let avatar { items.append(URLQueryItem(name: "avatar", value: avatar)) }
|
|
if let gender { items.append(URLQueryItem(name: "gender", value: gender)) }
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/accounts/\(userId)",
|
|
method: "PUT",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func listConversations() async throws -> [ConversationData] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let result: [ConversationData] = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
_conversations = result
|
|
conversationDelegate?.conversationsDidChange(_conversations)
|
|
return result
|
|
}
|
|
|
|
public func markRead(targetId: String, chatType: ChatType = .single) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/read",
|
|
method: "PUT",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func setConversationPinned(targetId: String, chatType: ChatType, pinned: Bool) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/pinned",
|
|
method: "PUT",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
URLQueryItem(name: "pinned", value: String(pinned)),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func setConversationMuted(targetId: String, chatType: ChatType, muted: Bool) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/muted",
|
|
method: "PUT",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
URLQueryItem(name: "muted", value: String(muted)),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func setConversationHidden(targetId: String, chatType: ChatType, hidden: Bool) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/hidden",
|
|
method: "PUT",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
URLQueryItem(name: "hidden", value: String(hidden)),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func setConversationGroup(targetId: String, chatType: ChatType, groupName: String? = nil) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
]
|
|
if let groupName {
|
|
items.append(URLQueryItem(name: "groupName", value: groupName))
|
|
}
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/group",
|
|
method: "PUT",
|
|
queryItems: items
|
|
)
|
|
}
|
|
|
|
public func listConversationGroups() async throws -> [String] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/conversation-groups",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func listConversationGroupItems(_ groupName: String) async throws -> [ConversationGroupItem] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/conversation-groups/\(groupName)",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func setDraft(targetId: String, chatType: ChatType, draft: String) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)/draft",
|
|
method: "PUT",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
URLQueryItem(name: "draft", value: draft),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func deleteConversation(targetId: String, chatType: ChatType) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/conversations/\(targetId)",
|
|
method: "DELETE",
|
|
queryItems: [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "chatType", value: chatType.rawValue),
|
|
]
|
|
)
|
|
}
|
|
|
|
public func getTotalUnreadCount() async throws -> Int {
|
|
let conversations = try await listConversations()
|
|
return conversations.reduce(0) { $0 + $1.unreadCount }
|
|
}
|
|
|
|
public func offlineMessageCount() async throws -> Int {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let result: [String: Int] = try await ApiClient.shared.request(
|
|
path: "/api/im/messages/offline/count",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
return result["count"] ?? 0
|
|
}
|
|
|
|
public func syncOfflineMessages() async throws -> [ImMessage] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/messages/offline",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)]
|
|
)
|
|
}
|
|
|
|
public func adminGroupReadReceipts(groupId: String, messageIds: [String]) async throws -> [GroupReadReceiptSummary] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
return try await ApiClient.shared.request(
|
|
path: "/api/im/admin/groups/\(groupId)/read-receipts",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: ImGroupReadReceiptRequest(messageIds: messageIds)
|
|
)
|
|
}
|
|
|
|
public func batchAddFriends(friendIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends/batch",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchFriendIdsRequest(friendIds: friendIds)
|
|
)
|
|
}
|
|
|
|
public func batchRemoveFriends(friendIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friends/batch/remove",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchFriendIdsRequest(friendIds: friendIds)
|
|
)
|
|
}
|
|
|
|
public func batchAcceptFriendRequests(requestIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests/batch/accept",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchRequestIdsRequest(requestIds: requestIds)
|
|
)
|
|
}
|
|
|
|
public func batchRejectFriendRequests(requestIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/friend-requests/batch/reject",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchRequestIdsRequest(requestIds: requestIds)
|
|
)
|
|
}
|
|
|
|
public func batchAddGroupMembers(groupId: String, userIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/batch",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchUserIdsRequest(userIds: userIds)
|
|
)
|
|
}
|
|
|
|
public func batchRemoveGroupMembers(groupId: String, userIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/batch/remove",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchUserIdsRequest(userIds: userIds)
|
|
)
|
|
}
|
|
|
|
public func batchAcceptGroupJoinRequests(groupId: String, requestIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests/batch/accept",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchRequestIdsRequest(requestIds: requestIds)
|
|
)
|
|
}
|
|
|
|
public func batchRejectGroupJoinRequests(groupId: String, requestIds: [String]) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/join-requests/batch/reject",
|
|
method: "POST",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: BatchRequestIdsRequest(requestIds: requestIds)
|
|
)
|
|
}
|
|
|
|
public func modifyGroupMemberInfo(groupId: String, userId: String, nickname: String? = nil, role: String? = nil) async throws {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
let _: EmptyResponse = try await ApiClient.shared.request(
|
|
path: "/api/im/groups/\(groupId)/members/\(userId)/info",
|
|
method: "PUT",
|
|
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
|
body: ModifyMemberInfoRequest(nickname: nickname, role: role)
|
|
)
|
|
}
|
|
|
|
public func disconnect() {
|
|
client?.disconnect()
|
|
client = nil
|
|
updateConnectionState(.disconnected)
|
|
}
|
|
|
|
private func fetchHistoryInternal(
|
|
toId: String? = nil,
|
|
groupId: String? = nil,
|
|
page: Int,
|
|
size: Int,
|
|
msgType: MsgType?,
|
|
keyword: String?,
|
|
startTime: Date?,
|
|
endTime: Date?,
|
|
isGroup: Bool = false
|
|
) async throws -> [ImMessage] {
|
|
let config = XuqmSDK.shared.requireConfig()
|
|
var items = [
|
|
URLQueryItem(name: "appId", value: config.appId),
|
|
URLQueryItem(name: "page", value: String(page)),
|
|
URLQueryItem(name: "size", value: String(size)),
|
|
]
|
|
if let msgType { items.append(URLQueryItem(name: "msgType", value: msgType.rawValue)) }
|
|
if let keyword { items.append(URLQueryItem(name: "keyword", value: keyword)) }
|
|
if let startTime { items.append(URLQueryItem(name: "startTime", value: isoLocalDateTime(startTime))) }
|
|
if let endTime { items.append(URLQueryItem(name: "endTime", value: isoLocalDateTime(endTime))) }
|
|
|
|
let path = isGroup
|
|
? "/api/im/messages/group-history/\(groupId ?? "")"
|
|
: "/api/im/messages/history/\(toId ?? "")"
|
|
return try await ApiClient.shared.request(path: path, queryItems: items)
|
|
}
|
|
|
|
private func jsonString(from object: [String: Any?]) -> String {
|
|
var payload: [String: Any] = [:]
|
|
for (key, value) in object {
|
|
if let value { payload[key] = value }
|
|
}
|
|
guard JSONSerialization.isValidJSONObject(payload),
|
|
let data = try? JSONSerialization.data(withJSONObject: payload, options: []),
|
|
let text = String(data: data, encoding: .utf8) else {
|
|
return "{}"
|
|
}
|
|
return text
|
|
}
|
|
|
|
private func isoLocalDateTime(_ date: Date) -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.calendar = Calendar(identifier: .gregorian)
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.timeZone = .current
|
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
|
return formatter.string(from: date)
|
|
}
|
|
|
|
private func getConversationTargetId(_ message: ImMessage) -> String {
|
|
if message.chatType == .group {
|
|
return message.toId
|
|
} else {
|
|
return message.fromUserId == currentUserId ? message.toId : message.fromUserId
|
|
}
|
|
}
|
|
|
|
private func updateConversations(with message: ImMessage) {
|
|
let targetId = getConversationTargetId(message)
|
|
let chatType = message.chatType
|
|
if let index = _conversations.firstIndex(where: { $0.targetId == targetId && $0.chatType == chatType }) {
|
|
let existing = _conversations[index]
|
|
_conversations[index] = ConversationData(
|
|
targetId: targetId,
|
|
chatType: chatType,
|
|
conversationGroup: existing.conversationGroup,
|
|
lastMsgContent: message.content,
|
|
lastMsgType: message.msgType.rawValue,
|
|
lastMsgTime: message.createdAt,
|
|
unreadCount: message.fromUserId == currentUserId ? existing.unreadCount : existing.unreadCount + 1,
|
|
isMuted: existing.isMuted,
|
|
isPinned: existing.isPinned
|
|
)
|
|
} else {
|
|
_conversations.append(ConversationData(
|
|
targetId: targetId,
|
|
chatType: chatType,
|
|
lastMsgContent: message.content,
|
|
lastMsgType: message.msgType.rawValue,
|
|
lastMsgTime: message.createdAt,
|
|
unreadCount: message.fromUserId == currentUserId ? 0 : 1,
|
|
isMuted: false,
|
|
isPinned: false
|
|
))
|
|
}
|
|
_conversations.sort { $0.lastMsgTime > $1.lastMsgTime }
|
|
conversationDelegate?.conversationsDidChange(_conversations)
|
|
}
|
|
|
|
private func markConversationRead(_ message: ImMessage) {
|
|
let targetId = getConversationTargetId(message)
|
|
let chatType = message.chatType
|
|
guard let index = _conversations.firstIndex(where: { $0.targetId == targetId && $0.chatType == chatType }) else { return }
|
|
let existing = _conversations[index]
|
|
_conversations[index] = ConversationData(
|
|
targetId: targetId,
|
|
chatType: chatType,
|
|
conversationGroup: existing.conversationGroup,
|
|
lastMsgContent: existing.lastMsgContent,
|
|
lastMsgType: existing.lastMsgType,
|
|
lastMsgTime: existing.lastMsgTime,
|
|
unreadCount: 0,
|
|
isMuted: existing.isMuted,
|
|
isPinned: existing.isPinned
|
|
)
|
|
conversationDelegate?.conversationsDidChange(_conversations)
|
|
}
|
|
|
|
private func handleRevokedMessage(_ message: ImMessage) {
|
|
let targetId = getConversationTargetId(message)
|
|
let chatType = message.chatType
|
|
guard let index = _conversations.firstIndex(where: { $0.targetId == targetId && $0.chatType == chatType }) else { return }
|
|
let existing = _conversations[index]
|
|
let updated: ConversationData
|
|
if existing.lastMsgTime == message.createdAt {
|
|
updated = ConversationData(
|
|
targetId: targetId,
|
|
chatType: chatType,
|
|
conversationGroup: existing.conversationGroup,
|
|
lastMsgContent: "消息已撤回",
|
|
lastMsgType: MsgType.revoked.rawValue,
|
|
lastMsgTime: existing.lastMsgTime,
|
|
unreadCount: existing.unreadCount,
|
|
isMuted: existing.isMuted,
|
|
isPinned: existing.isPinned
|
|
)
|
|
} else {
|
|
updated = existing
|
|
}
|
|
_conversations[index] = updated
|
|
conversationDelegate?.conversationsDidChange(_conversations)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
extension ImSDK: ImEventDelegate {
|
|
public func imClientDidConnect() {
|
|
updateConnectionState(.connected)
|
|
delegate?.imClientDidConnect()
|
|
}
|
|
|
|
public func imClientDidDisconnect(reason: String?) {
|
|
updateConnectionState(.disconnected)
|
|
delegate?.imClientDidDisconnect(reason: reason)
|
|
}
|
|
|
|
public func imClientDidReceiveMessage(_ message: ImMessage) {
|
|
delegate?.imClientDidReceiveMessage(message)
|
|
updateConversations(with: message)
|
|
}
|
|
|
|
public func imClientDidReceiveGroupMessage(_ message: ImMessage) {
|
|
delegate?.imClientDidReceiveGroupMessage(message)
|
|
updateConversations(with: message)
|
|
}
|
|
|
|
public func imClientDidReadMessage(_ message: ImMessage) {
|
|
delegate?.imClientDidReadMessage(message)
|
|
markConversationRead(message)
|
|
}
|
|
|
|
public func imClientDidReceiveRevokedMessage(_ message: ImMessage) {
|
|
delegate?.imClientDidReceiveRevokedMessage(message)
|
|
handleRevokedMessage(message)
|
|
}
|
|
|
|
public func imClientDidError(_ error: String) {
|
|
updateConnectionState(.disconnected)
|
|
delegate?.imClientDidError(error)
|
|
}
|
|
}
|