feat(im): 添加即时通讯功能模块
- 添加了 IM API 接口定义,包含登录、消息、群组、好友等接口 - 实现了 ImSDK 核心功能,支持发送各类消息和管理会话 - 集成了 WebSocket 连接管理和自动重连机制 - 添加了本地联系人缓存并优化对话标题显示逻辑 - 实现了 HarmonyOS 平台 HTTP 客户端基础功能
这个提交包含在:
父节点
2ca58aa458
当前提交
765d8a0333
@ -16,6 +16,7 @@ export interface XuqmConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _config: XuqmConfig | null = null
|
let _config: XuqmConfig | null = null
|
||||||
|
let _userId: string | null = null
|
||||||
|
|
||||||
export function initConfigFromRemote(
|
export function initConfigFromRemote(
|
||||||
options: XuqmInitOptions,
|
options: XuqmInitOptions,
|
||||||
@ -37,6 +38,14 @@ export function getConfig(): XuqmConfig {
|
|||||||
return _config
|
return _config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setUserId(userId: string | null): void {
|
||||||
|
_userId = userId
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserId(): string | null {
|
||||||
|
return _userId
|
||||||
|
}
|
||||||
|
|
||||||
export function isInitialized(): boolean {
|
export function isInitialized(): boolean {
|
||||||
return _config !== null
|
return _config !== null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export { XuqmSDK } from './sdk'
|
export { XuqmSDK } from './sdk'
|
||||||
export type { XuqmInitOptions, XuqmConfig } from './config'
|
export type { XuqmInitOptions, XuqmConfig } from './config'
|
||||||
export { getConfig, isInitialized } from './config'
|
export { getConfig, isInitialized, setUserId, getUserId } from './config'
|
||||||
export { apiRequest, _getToken, _saveToken, _clearToken } from './http'
|
export { apiRequest, _getToken, _saveToken, _clearToken } from './http'
|
||||||
export { API_BASE_URL, IM_WS_URL } from './constants'
|
export { API_BASE_URL, IM_WS_URL } from './constants'
|
||||||
export { getDeviceId, getDeviceInfo, detectPushVendor } from './device'
|
export { getDeviceId, getDeviceInfo, detectPushVendor } from './device'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { initConfigFromRemote, isInitialized, type XuqmInitOptions } from './config'
|
import { initConfigFromRemote, isInitialized, type XuqmInitOptions, setUserId as setCommonUserId, getUserId as getCommonUserId } from './config'
|
||||||
|
|
||||||
export const XuqmSDK = {
|
export const XuqmSDK = {
|
||||||
/**
|
/**
|
||||||
@ -50,4 +50,12 @@ export const XuqmSDK = {
|
|||||||
apiBaseUrl: options.serverUrl,
|
apiBaseUrl: options.serverUrl,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setUserId(userId: string | null): void {
|
||||||
|
setCommonUserId(userId)
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserId(): string | null {
|
||||||
|
return getCommonUserId()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { _getToken, getConfig } from '@xuqm/rn-common'
|
import { _getToken, getConfig, getUserId } from '@xuqm/rn-common'
|
||||||
import type { ImEventListener, ImMessage, SendMessageParams } from './types'
|
import type { ImEventListener, ImMessage, SendMessageParams } from './types'
|
||||||
|
|
||||||
interface StompFrame {
|
interface StompFrame {
|
||||||
@ -50,8 +50,8 @@ export class ImClient {
|
|||||||
msgType: SendMessageParams['msgType'],
|
msgType: SendMessageParams['msgType'],
|
||||||
content: string,
|
content: string,
|
||||||
mentionedUserIds?: string,
|
mentionedUserIds?: string,
|
||||||
) {
|
): ImMessage {
|
||||||
this.send({
|
return this.send({
|
||||||
toId,
|
toId,
|
||||||
chatType,
|
chatType,
|
||||||
msgType,
|
msgType,
|
||||||
@ -60,13 +60,14 @@ export class ImClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
send(params: SendMessageParams) {
|
send(params: SendMessageParams): ImMessage {
|
||||||
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
||||||
throw new Error('IM not connected')
|
|
||||||
}
|
|
||||||
if (!this.activeAppId) {
|
if (!this.activeAppId) {
|
||||||
throw new Error('IM appId not configured')
|
throw new Error('IM appId not configured')
|
||||||
}
|
}
|
||||||
|
const outgoing = this.buildOutgoingMessage(params)
|
||||||
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
||||||
|
return { ...outgoing, status: 'FAILED' }
|
||||||
|
}
|
||||||
|
|
||||||
this.sendFrame(
|
this.sendFrame(
|
||||||
'SEND',
|
'SEND',
|
||||||
@ -76,6 +77,7 @@ export class ImClient {
|
|||||||
},
|
},
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
appId: this.activeAppId,
|
appId: this.activeAppId,
|
||||||
|
messageId: outgoing.id,
|
||||||
toId: params.toId,
|
toId: params.toId,
|
||||||
chatType: params.chatType,
|
chatType: params.chatType,
|
||||||
msgType: params.msgType,
|
msgType: params.msgType,
|
||||||
@ -83,6 +85,8 @@ export class ImClient {
|
|||||||
mentionedUserIds: params.mentionedUserIds ?? '',
|
mentionedUserIds: params.mentionedUserIds ?? '',
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return outgoing
|
||||||
}
|
}
|
||||||
|
|
||||||
revoke(messageId: string) {
|
revoke(messageId: string) {
|
||||||
@ -205,7 +209,7 @@ export class ImClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frame.command === 'MESSAGE') {
|
if (frame.command === 'MESSAGE') {
|
||||||
const message: ImMessage = JSON.parse(frame.body)
|
const message: ImMessage = this.normalizeMessage(JSON.parse(frame.body))
|
||||||
if (message.chatType === 'GROUP') {
|
if (message.chatType === 'GROUP') {
|
||||||
this.listeners.forEach(listener => listener.onGroupMessage?.(message))
|
this.listeners.forEach(listener => listener.onGroupMessage?.(message))
|
||||||
return
|
return
|
||||||
@ -253,4 +257,38 @@ export class ImClient {
|
|||||||
return { command, headers, body }
|
return { command, headers, body }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildOutgoingMessage(params: SendMessageParams): ImMessage {
|
||||||
|
const userId = getUserId() ?? ''
|
||||||
|
const messageId = params.messageId ?? this.generateMessageId()
|
||||||
|
return {
|
||||||
|
id: messageId,
|
||||||
|
appId: this.activeAppId ?? getConfig().appId,
|
||||||
|
fromUserId: userId,
|
||||||
|
fromId: userId,
|
||||||
|
toId: params.toId,
|
||||||
|
chatType: params.chatType,
|
||||||
|
msgType: params.msgType,
|
||||||
|
content: params.content,
|
||||||
|
status: 'SENDING',
|
||||||
|
mentionedUserIds: params.mentionedUserIds,
|
||||||
|
groupReadCount: 0,
|
||||||
|
revoked: false,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateMessageId(): string {
|
||||||
|
const cryptoId = globalThis.crypto?.randomUUID?.()
|
||||||
|
return cryptoId ?? `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeMessage(message: ImMessage): ImMessage {
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
fromId: message.fromId ?? message.fromUserId,
|
||||||
|
revoked: message.revoked ?? message.status === 'REVOKED',
|
||||||
|
appId: message.appId ?? (this.activeAppId ?? getConfig().appId),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { apiRequest, _getToken, _saveToken, getConfig, getDeviceInfo } from '@xuqm/rn-common'
|
import { apiRequest, _getToken, _saveToken, getConfig, getDeviceInfo, setUserId as setCommonUserId, getUserId as getCommonUserId } from '@xuqm/rn-common'
|
||||||
import { ImClient } from './ImClient'
|
import { ImClient } from './ImClient'
|
||||||
import { ImDatabase } from './db/ImDatabase'
|
import { ImDatabase } from './db/ImDatabase'
|
||||||
import type { MessageSearchParams } from './db/ImDatabase'
|
import type { MessageSearchParams } from './db/ImDatabase'
|
||||||
@ -12,12 +12,56 @@ import type {
|
|||||||
ImGroup,
|
ImGroup,
|
||||||
ImMessage,
|
ImMessage,
|
||||||
MsgType,
|
MsgType,
|
||||||
|
UserProfile,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { uploadFile } from './upload'
|
import { uploadFile } from './upload'
|
||||||
|
|
||||||
let client: ImClient | null = null
|
let client: ImClient | null = null
|
||||||
let _currentUserId: string | null = null
|
let _currentUserId: string | null = null
|
||||||
|
|
||||||
|
function generateMessageId(): string {
|
||||||
|
const cryptoId = globalThis.crypto?.randomUUID?.()
|
||||||
|
return cryptoId ?? `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMessage(msg: ImMessage, fallback?: Partial<ImMessage>): ImMessage {
|
||||||
|
return {
|
||||||
|
...fallback,
|
||||||
|
...msg,
|
||||||
|
appId: msg.appId ?? fallback?.appId ?? getConfig().appId,
|
||||||
|
fromId: msg.fromId ?? fallback?.fromId ?? msg.fromUserId,
|
||||||
|
revoked: msg.revoked ?? msg.status === 'REVOKED',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOutgoingMessage(params: {
|
||||||
|
messageId?: string
|
||||||
|
toId: string
|
||||||
|
chatType: ChatType
|
||||||
|
msgType: MsgType
|
||||||
|
content: string
|
||||||
|
mentionedUserIds?: string
|
||||||
|
}): ImMessage {
|
||||||
|
const config = getConfig()
|
||||||
|
const fromId = _currentUserId ?? getCommonUserId() ?? ''
|
||||||
|
const id = params.messageId ?? generateMessageId()
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
appId: config.appId,
|
||||||
|
fromUserId: fromId,
|
||||||
|
fromId,
|
||||||
|
toId: params.toId,
|
||||||
|
chatType: params.chatType,
|
||||||
|
msgType: params.msgType,
|
||||||
|
content: params.content,
|
||||||
|
status: 'SENDING',
|
||||||
|
mentionedUserIds: params.mentionedUserIds,
|
||||||
|
groupReadCount: 0,
|
||||||
|
revoked: false,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function _syncHistoryForAllConversations(): Promise<void> {
|
async function _syncHistoryForAllConversations(): Promise<void> {
|
||||||
if (!ImDatabase.isInitialized() || !_currentUserId) return
|
if (!ImDatabase.isInitialized() || !_currentUserId) return
|
||||||
|
|
||||||
@ -124,6 +168,7 @@ export const ImSDK = {
|
|||||||
})
|
})
|
||||||
await _saveToken(res.token)
|
await _saveToken(res.token)
|
||||||
_currentUserId = userId
|
_currentUserId = userId
|
||||||
|
setCommonUserId(userId)
|
||||||
|
|
||||||
if (dbName !== undefined || ImDatabase.isInitialized()) {
|
if (dbName !== undefined || ImDatabase.isInitialized()) {
|
||||||
ImDatabase.init(dbName ?? 'xuqm_im')
|
ImDatabase.init(dbName ?? 'xuqm_im')
|
||||||
@ -146,6 +191,7 @@ export const ImSDK = {
|
|||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
await _saveToken(token)
|
await _saveToken(token)
|
||||||
_currentUserId = userId
|
_currentUserId = userId
|
||||||
|
setCommonUserId(userId)
|
||||||
|
|
||||||
if (dbName !== undefined || ImDatabase.isInitialized()) {
|
if (dbName !== undefined || ImDatabase.isInitialized()) {
|
||||||
ImDatabase.init(dbName ?? 'xuqm_im')
|
ImDatabase.init(dbName ?? 'xuqm_im')
|
||||||
@ -294,15 +340,38 @@ export const ImSDK = {
|
|||||||
mentionedUserIds?: string,
|
mentionedUserIds?: string,
|
||||||
): Promise<ImMessage> {
|
): Promise<ImMessage> {
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
const msg = await apiRequest<ImMessage>('/api/im/messages/send', {
|
const outgoing = buildOutgoingMessage({
|
||||||
method: 'POST',
|
toId,
|
||||||
params: { appId: config.appId },
|
chatType,
|
||||||
body: { toId, chatType, msgType, content, mentionedUserIds: mentionedUserIds ?? '' },
|
msgType,
|
||||||
|
content,
|
||||||
|
mentionedUserIds,
|
||||||
})
|
})
|
||||||
if (ImDatabase.isInitialized() && _currentUserId) {
|
try {
|
||||||
await ImDatabase.saveMessage(msg, _currentUserId)
|
const msg = await apiRequest<ImMessage>('/api/im/messages/send', {
|
||||||
|
method: 'POST',
|
||||||
|
params: { appId: config.appId },
|
||||||
|
body: {
|
||||||
|
toId,
|
||||||
|
chatType,
|
||||||
|
msgType,
|
||||||
|
content,
|
||||||
|
mentionedUserIds: mentionedUserIds ?? '',
|
||||||
|
messageId: outgoing.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const finalMsg = normalizeMessage(msg, outgoing)
|
||||||
|
if (ImDatabase.isInitialized() && _currentUserId) {
|
||||||
|
await ImDatabase.saveMessage(finalMsg, _currentUserId)
|
||||||
|
}
|
||||||
|
return finalMsg
|
||||||
|
} catch (error) {
|
||||||
|
const failed = { ...outgoing, status: 'FAILED' as const }
|
||||||
|
if (ImDatabase.isInitialized() && _currentUserId) {
|
||||||
|
await ImDatabase.saveMessage(failed, _currentUserId)
|
||||||
|
}
|
||||||
|
return failed
|
||||||
}
|
}
|
||||||
return msg
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendTextMessage(
|
async sendTextMessage(
|
||||||
@ -457,7 +526,7 @@ export const ImSDK = {
|
|||||||
params: { appId: config.appId },
|
params: { appId: config.appId },
|
||||||
})
|
})
|
||||||
if (ImDatabase.isInitialized() && _currentUserId) {
|
if (ImDatabase.isInitialized() && _currentUserId) {
|
||||||
await ImDatabase.saveMessage(msg, _currentUserId)
|
await ImDatabase.saveMessage(normalizeMessage(msg), _currentUserId)
|
||||||
}
|
}
|
||||||
return msg
|
return msg
|
||||||
},
|
},
|
||||||
@ -669,6 +738,31 @@ export const ImSDK = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getProfile(userId: string): Promise<UserProfile> {
|
||||||
|
const config = getConfig()
|
||||||
|
return apiRequest<UserProfile>(`/api/im/accounts/${encodeURIComponent(userId)}`, {
|
||||||
|
params: { appId: config.appId },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateProfile(
|
||||||
|
userId: string,
|
||||||
|
nickname?: string,
|
||||||
|
avatar?: string,
|
||||||
|
gender?: string,
|
||||||
|
): Promise<UserProfile> {
|
||||||
|
const config = getConfig()
|
||||||
|
return apiRequest<UserProfile>(`/api/im/accounts/${encodeURIComponent(userId)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
params: {
|
||||||
|
appId: config.appId,
|
||||||
|
...(nickname ? { nickname } : {}),
|
||||||
|
...(avatar ? { avatar } : {}),
|
||||||
|
...(gender ? { gender } : {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
async listConversations(): Promise<ConversationData[]> {
|
async listConversations(): Promise<ConversationData[]> {
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
if (ImDatabase.isInitialized()) {
|
if (ImDatabase.isInitialized()) {
|
||||||
@ -802,15 +896,15 @@ export const ImSDK = {
|
|||||||
...listener,
|
...listener,
|
||||||
onMessage: async (msg) => {
|
onMessage: async (msg) => {
|
||||||
if (ImDatabase.isInitialized() && _currentUserId) {
|
if (ImDatabase.isInitialized() && _currentUserId) {
|
||||||
await ImDatabase.saveMessage(msg, _currentUserId)
|
await ImDatabase.saveMessage(normalizeMessage(msg), _currentUserId)
|
||||||
}
|
}
|
||||||
listener.onMessage?.(msg)
|
listener.onMessage?.(normalizeMessage(msg))
|
||||||
},
|
},
|
||||||
onGroupMessage: async (msg) => {
|
onGroupMessage: async (msg) => {
|
||||||
if (ImDatabase.isInitialized() && _currentUserId) {
|
if (ImDatabase.isInitialized() && _currentUserId) {
|
||||||
await ImDatabase.saveMessage(msg, _currentUserId)
|
await ImDatabase.saveMessage(normalizeMessage(msg), _currentUserId)
|
||||||
}
|
}
|
||||||
listener.onGroupMessage?.(msg)
|
listener.onGroupMessage?.(normalizeMessage(msg))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -835,5 +929,6 @@ export const ImSDK = {
|
|||||||
client?.disconnect()
|
client?.disconnect()
|
||||||
client = null
|
client = null
|
||||||
_currentUserId = null
|
_currentUserId = null
|
||||||
|
setCommonUserId(null)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,9 +12,12 @@ export type {
|
|||||||
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
|
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
|
||||||
ImEventListener, SendMessageParams,
|
ImEventListener, SendMessageParams,
|
||||||
ConversationData,
|
ConversationData,
|
||||||
|
HistoryQuery,
|
||||||
|
PageResult,
|
||||||
FriendRequest,
|
FriendRequest,
|
||||||
GroupJoinRequest,
|
GroupJoinRequest,
|
||||||
BlacklistEntry,
|
BlacklistEntry,
|
||||||
|
UserProfile,
|
||||||
} from './types'
|
} from './types'
|
||||||
export { uploadFile } from './upload'
|
export { uploadFile } from './upload'
|
||||||
export type { UploadResult } from './upload'
|
export type { UploadResult } from './upload'
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export interface ImMessage {
|
|||||||
id: string
|
id: string
|
||||||
appId: string
|
appId: string
|
||||||
fromUserId: string
|
fromUserId: string
|
||||||
|
fromId?: string
|
||||||
toId: string
|
toId: string
|
||||||
chatType: ChatType
|
chatType: ChatType
|
||||||
msgType: MsgType
|
msgType: MsgType
|
||||||
@ -30,6 +31,7 @@ export interface ImMessage {
|
|||||||
status: MsgStatus
|
status: MsgStatus
|
||||||
mentionedUserIds?: string
|
mentionedUserIds?: string
|
||||||
groupReadCount?: number
|
groupReadCount?: number
|
||||||
|
revoked?: boolean
|
||||||
createdAt: number
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ export interface ImEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SendMessageParams {
|
export interface SendMessageParams {
|
||||||
|
messageId?: string
|
||||||
toId: string
|
toId: string
|
||||||
chatType: ChatType
|
chatType: ChatType
|
||||||
msgType: MsgType
|
msgType: MsgType
|
||||||
@ -72,6 +75,27 @@ export interface ConversationData {
|
|||||||
isPinned: boolean
|
isPinned: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HistoryQuery {
|
||||||
|
msgType?: MsgType
|
||||||
|
keyword?: string
|
||||||
|
startTime?: Date | string | number
|
||||||
|
endTime?: Date | string | number
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageResult<T> {
|
||||||
|
content: T[]
|
||||||
|
totalElements: number
|
||||||
|
totalPages: number
|
||||||
|
size: number
|
||||||
|
number: number
|
||||||
|
numberOfElements: number
|
||||||
|
first: boolean
|
||||||
|
last: boolean
|
||||||
|
empty: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface FriendRequest {
|
export interface FriendRequest {
|
||||||
id: string
|
id: string
|
||||||
appId: string
|
appId: string
|
||||||
@ -101,3 +125,14 @@ export interface BlacklistEntry {
|
|||||||
blockedUserId: string
|
blockedUserId: string
|
||||||
createdAt: number
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserProfile {
|
||||||
|
id?: string
|
||||||
|
appId?: string
|
||||||
|
userId: string
|
||||||
|
nickname?: string | null
|
||||||
|
avatar?: string | null
|
||||||
|
gender?: string | null
|
||||||
|
status?: string | null
|
||||||
|
createdAt?: number | null
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
export { XuqmSDK } from '../packages/common/src'
|
export { XuqmSDK } from '../packages/common/src'
|
||||||
export type { XuqmInitOptions, DeviceInfo } from '../packages/common/src'
|
export type { XuqmInitOptions, DeviceInfo } from '../packages/common/src'
|
||||||
export { getDeviceId, getDeviceInfo, detectPushVendor } from '../packages/common/src'
|
export { getDeviceId, getDeviceInfo, detectPushVendor, setUserId, getUserId } from '../packages/common/src'
|
||||||
export { ScaledImage } from '../packages/common/src'
|
export { ScaledImage } from '../packages/common/src'
|
||||||
|
|
||||||
export { ImSDK } from '../packages/im/src'
|
export { ImSDK } from '../packages/im/src'
|
||||||
@ -15,6 +15,8 @@ export type {
|
|||||||
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
|
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
|
||||||
ImEventListener, SendMessageParams,
|
ImEventListener, SendMessageParams,
|
||||||
ConversationData,
|
ConversationData,
|
||||||
|
HistoryQuery,
|
||||||
|
PageResult,
|
||||||
FriendRequest,
|
FriendRequest,
|
||||||
GroupJoinRequest,
|
GroupJoinRequest,
|
||||||
BlacklistEntry,
|
BlacklistEntry,
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户