diff --git a/Sources/XuqmSDK/IM/ImSDK.swift b/Sources/XuqmSDK/IM/ImSDK.swift index 6651627..473471f 100644 --- a/Sources/XuqmSDK/IM/ImSDK.swift +++ b/Sources/XuqmSDK/IM/ImSDK.swift @@ -540,6 +540,30 @@ public final class ImSDK { ) } + 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)", @@ -604,6 +628,15 @@ public final class ImSDK { ) } + 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( @@ -613,6 +646,35 @@ public final class ImSDK { ) } + 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( @@ -688,6 +750,17 @@ public final class ImSDK { ) } + 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( @@ -760,6 +833,51 @@ public final class ImSDK { ) } + 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( @@ -790,6 +908,16 @@ public final class ImSDK { return conversations.reduce(0) { $0 + $1.unreadCount } } + 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 disconnect() { client?.disconnect() client = nil diff --git a/Sources/XuqmSDK/IM/ImTypes.swift b/Sources/XuqmSDK/IM/ImTypes.swift index ec791eb..372e6d4 100644 --- a/Sources/XuqmSDK/IM/ImTypes.swift +++ b/Sources/XuqmSDK/IM/ImTypes.swift @@ -91,12 +91,15 @@ public struct ImGroup: Codable, Sendable { public let memberIds: String public let adminIds: String public let announcement: String? + public let memberInfo: String? + public let extAttributes: String? public let createdAt: Int64 } public struct ConversationData: Codable, Sendable { public let targetId: String public let chatType: ChatType + public let conversationGroup: String? public let lastMsgContent: String? public let lastMsgType: String? public let lastMsgTime: Int64 @@ -105,6 +108,12 @@ public struct ConversationData: Codable, Sendable { public let isPinned: Bool } +public struct ConversationGroupItem: Codable, Sendable { + public let targetId: String + public let chatType: ChatType + public let groupName: String +} + public struct FriendRequest: Codable, Sendable { public let id: String public let appId: String @@ -135,6 +144,21 @@ public struct BlacklistEntry: Codable, Sendable { public let createdAt: Int64 } +public struct BlacklistCheckResult: Codable, Sendable { + public let targetUserId: String + public let blockedByMe: Bool + public let blockedByTarget: Bool + public let eitherBlocked: Bool +} + +public struct GroupReadReceiptSummary: Codable, Sendable { + public let messageId: String + public let groupId: String + public let memberCount: Int + public let readCount: Int + public let unreadCount: Int +} + public struct UserProfile: Codable, Sendable { public let id: String? public let appId: String? @@ -187,3 +211,78 @@ public struct ImMuteMemberRequest: Encodable, Sendable { public let userId: String public let minutes: Int64 } + +public struct ImTransferOwnerRequest: Encodable, Sendable { + public let newOwnerId: String +} + +public struct ImAttributeKeysRequest: Encodable, Sendable { + public let keys: [String] +} + +public struct ImGroupReadReceiptRequest: Encodable, Sendable { + public let messageIds: [String] +} + +public enum ImAttributeValue: Codable, Sendable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case null + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let value = try? container.decode(Bool.self) { + self = .bool(value) + } else if let value = try? container.decode(Int.self) { + self = .int(value) + } else if let value = try? container.decode(Double.self) { + self = .double(value) + } else { + self = .string(try container.decode(String.self)) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let value): + try container.encode(value) + case .int(let value): + try container.encode(value) + case .double(let value): + try container.encode(value) + case .bool(let value): + try container.encode(value) + case .null: + try container.encodeNil() + } + } +} + +extension ImAttributeValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension ImAttributeValue: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .int(value) + } +} + +extension ImAttributeValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { + self = .double(value) + } +} + +extension ImAttributeValue: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +}