2017-10-12 10:00:28 +08:00
|
|
|
import Users from '/imports/api/users';
|
|
|
|
import Chat from '/imports/api/chat';
|
|
|
|
import Meetings from '/imports/api/meetings';
|
2016-07-07 20:50:32 +08:00
|
|
|
import Auth from '/imports/ui/services/auth';
|
2016-07-11 20:34:58 +08:00
|
|
|
import UnreadMessages from '/imports/ui/services/unread-messages';
|
2017-03-01 06:40:16 +08:00
|
|
|
import Storage from '/imports/ui/services/storage/session';
|
2017-08-01 21:10:12 +08:00
|
|
|
import mapUser from '/imports/ui/services/user/mapUser';
|
2017-11-21 18:50:20 +08:00
|
|
|
import { EMOJI_STATUSES } from '/imports/utils/statuses';
|
2017-09-29 02:19:57 +08:00
|
|
|
import { makeCall } from '/imports/ui/services/api';
|
2017-03-22 05:52:10 +08:00
|
|
|
import _ from 'lodash';
|
2017-12-06 03:13:11 +08:00
|
|
|
import KEY_CODES from '/imports/utils/keyCodes';
|
2016-06-07 00:45:30 +08:00
|
|
|
|
2017-10-31 03:15:43 +08:00
|
|
|
const APP_CONFIG = Meteor.settings.public.app;
|
|
|
|
const ALLOW_MODERATOR_TO_UNMUTE_AUDIO = APP_CONFIG.allowModeratorToUnmuteAudio;
|
2017-10-26 20:50:55 +08:00
|
|
|
|
2016-08-17 23:48:03 +08:00
|
|
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
|
|
|
const PRIVATE_CHAT_TYPE = CHAT_CONFIG.type_private;
|
2017-07-13 04:15:50 +08:00
|
|
|
const PUBLIC_CHAT_USERID = CHAT_CONFIG.public_userid;
|
2016-06-07 00:45:30 +08:00
|
|
|
|
2017-03-17 23:27:37 +08:00
|
|
|
// session for closed chat list
|
|
|
|
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
|
|
|
|
|
2017-06-03 03:25:02 +08:00
|
|
|
const mapOpenChats = (chat) => {
|
|
|
|
const currentUserId = Auth.userID;
|
2017-08-05 01:58:55 +08:00
|
|
|
return chat.fromUserId !== currentUserId
|
|
|
|
? chat.fromUserId
|
|
|
|
: chat.toUserId;
|
2016-06-28 21:10:20 +08:00
|
|
|
};
|
|
|
|
|
2018-03-21 03:35:28 +08:00
|
|
|
const CUSTOM_LOGO_URL_KEY = 'CustomLogoUrl';
|
|
|
|
|
|
|
|
export const setCustomLogoUrl = path => Storage.setItem(CUSTOM_LOGO_URL_KEY, path);
|
|
|
|
|
|
|
|
const getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY);
|
|
|
|
|
2016-06-07 21:58:51 +08:00
|
|
|
const sortUsersByName = (a, b) => {
|
|
|
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return -1;
|
2016-06-07 21:58:51 +08:00
|
|
|
} else if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return 1;
|
2016-06-07 21:58:51 +08:00
|
|
|
} else if (a.id.toLowerCase() > b.id.toLowerCase()) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return -1;
|
2016-06-07 21:58:51 +08:00
|
|
|
} else if (a.id.toLowerCase() < b.id.toLowerCase()) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return 1;
|
2016-06-07 21:58:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortUsersByEmoji = (a, b) => {
|
2017-12-12 23:03:33 +08:00
|
|
|
const { status: statusA } = a.emoji;
|
|
|
|
const { status: statusB } = b.emoji;
|
2017-09-23 01:03:14 +08:00
|
|
|
|
2017-12-12 23:03:33 +08:00
|
|
|
const emojiA = statusA in EMOJI_STATUSES ? EMOJI_STATUSES[statusA] : statusA;
|
|
|
|
const emojiB = statusB in EMOJI_STATUSES ? EMOJI_STATUSES[statusB] : statusB;
|
|
|
|
|
2017-12-13 01:27:51 +08:00
|
|
|
if (emojiA && emojiB && (emojiA !== EMOJI_STATUSES.none && emojiB !== EMOJI_STATUSES.none)) {
|
2016-06-08 00:46:24 +08:00
|
|
|
if (a.emoji.changedAt < b.emoji.changedAt) {
|
|
|
|
return -1;
|
|
|
|
} else if (a.emoji.changedAt > b.emoji.changedAt) {
|
|
|
|
return 1;
|
|
|
|
}
|
2017-12-13 01:27:51 +08:00
|
|
|
} else if (emojiA && emojiA !== EMOJI_STATUSES.none) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return -1;
|
2017-12-13 01:27:51 +08:00
|
|
|
} else if (emojiB && emojiB !== EMOJI_STATUSES.none) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return 1;
|
|
|
|
}
|
2016-06-07 21:58:51 +08:00
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortUsersByModerator = (a, b) => {
|
|
|
|
if (a.isModerator && b.isModerator) {
|
2016-10-07 04:42:37 +08:00
|
|
|
return sortUsersByEmoji(a, b);
|
2016-06-07 21:58:51 +08:00
|
|
|
} else if (a.isModerator) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return -1;
|
2016-06-07 21:58:51 +08:00
|
|
|
} else if (b.isModerator) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return 1;
|
2016-06-07 21:58:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortUsersByPhoneUser = (a, b) => {
|
2016-06-08 00:46:24 +08:00
|
|
|
if (!a.isPhoneUser && !b.isPhoneUser) {
|
2017-12-12 23:03:33 +08:00
|
|
|
return 0;
|
2016-06-08 00:46:24 +08:00
|
|
|
} else if (!a.isPhoneUser) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return -1;
|
2016-06-08 00:46:24 +08:00
|
|
|
} else if (!b.isPhoneUser) {
|
2016-06-07 00:45:30 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2016-06-07 21:58:51 +08:00
|
|
|
const sortUsers = (a, b) => {
|
2016-10-07 04:42:37 +08:00
|
|
|
let sort = sortUsersByModerator(a, b);
|
2016-06-07 21:58:51 +08:00
|
|
|
|
|
|
|
if (sort === 0) {
|
2016-10-07 04:42:37 +08:00
|
|
|
sort = sortUsersByEmoji(a, b);
|
2016-06-07 21:58:51 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (sort === 0) {
|
|
|
|
sort = sortUsersByPhoneUser(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sort === 0) {
|
|
|
|
sort = sortUsersByName(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sort;
|
|
|
|
};
|
|
|
|
|
2016-06-28 21:10:20 +08:00
|
|
|
const sortChatsByName = (a, b) => {
|
|
|
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
|
|
|
return -1;
|
|
|
|
} else if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
|
|
|
return 1;
|
|
|
|
} else if (a.id.toLowerCase() > b.id.toLowerCase()) {
|
|
|
|
return -1;
|
|
|
|
} else if (a.id.toLowerCase() < b.id.toLowerCase()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sortChatsByIcon = (a, b) => {
|
|
|
|
if (a.icon && b.icon) {
|
|
|
|
return sortChatsByName(a, b);
|
|
|
|
} else if (a.icon) {
|
|
|
|
return -1;
|
|
|
|
} else if (b.icon) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2017-09-22 22:24:24 +08:00
|
|
|
const isPublicChat = chat => (
|
|
|
|
chat.id === 'public'
|
|
|
|
);
|
|
|
|
|
2016-06-28 21:10:20 +08:00
|
|
|
const sortChats = (a, b) => {
|
|
|
|
let sort = sortChatsByIcon(a, b);
|
|
|
|
|
|
|
|
if (sort === 0) {
|
|
|
|
sort = sortChatsByName(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sort;
|
|
|
|
};
|
|
|
|
|
|
|
|
const userFindSorting = {
|
2017-07-26 22:09:07 +08:00
|
|
|
emojiTime: 1,
|
|
|
|
role: 1,
|
|
|
|
phoneUser: 1,
|
|
|
|
sortName: 1,
|
|
|
|
userId: 1,
|
2016-06-28 21:10:20 +08:00
|
|
|
};
|
|
|
|
|
2016-06-07 00:45:30 +08:00
|
|
|
const getUsers = () => {
|
2017-06-03 03:25:02 +08:00
|
|
|
const users = Users
|
2017-07-26 22:09:07 +08:00
|
|
|
.find({ connectionStatus: 'online' }, userFindSorting)
|
2017-04-13 04:19:39 +08:00
|
|
|
.fetch();
|
2016-06-07 00:45:30 +08:00
|
|
|
|
|
|
|
return users
|
2017-04-13 04:19:39 +08:00
|
|
|
.map(mapUser)
|
|
|
|
.sort(sortUsers);
|
2016-05-31 19:29:38 +08:00
|
|
|
};
|
2016-05-20 02:22:56 +08:00
|
|
|
|
2017-06-03 03:25:02 +08:00
|
|
|
const getOpenChats = (chatID) => {
|
2016-06-28 21:10:20 +08:00
|
|
|
let openChats = Chat
|
2017-08-05 01:58:55 +08:00
|
|
|
.find({ type: PRIVATE_CHAT_TYPE })
|
2017-04-13 04:19:39 +08:00
|
|
|
.fetch()
|
|
|
|
.map(mapOpenChats);
|
2016-06-28 21:10:20 +08:00
|
|
|
|
2016-06-30 22:45:19 +08:00
|
|
|
if (chatID) {
|
|
|
|
openChats.push(chatID);
|
2016-06-28 21:10:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
openChats = _.uniq(openChats);
|
|
|
|
|
|
|
|
openChats = Users
|
2017-07-26 22:09:07 +08:00
|
|
|
.find({ userId: { $in: openChats } })
|
2017-04-13 04:19:39 +08:00
|
|
|
.map(mapUser)
|
2017-06-03 03:25:02 +08:00
|
|
|
.map((op) => {
|
2017-07-12 21:18:26 +08:00
|
|
|
const openChat = op;
|
|
|
|
openChat.unreadCounter = UnreadMessages.count(op.id);
|
|
|
|
return openChat;
|
2017-04-13 04:19:39 +08:00
|
|
|
});
|
2017-03-01 06:40:16 +08:00
|
|
|
|
2017-06-03 03:25:02 +08:00
|
|
|
const currentClosedChats = Storage.getItem(CLOSED_CHAT_LIST_KEY) || [];
|
|
|
|
const filteredChatList = [];
|
2017-03-01 06:55:00 +08:00
|
|
|
|
2017-03-03 04:33:49 +08:00
|
|
|
openChats.forEach((op) => {
|
2017-03-07 01:25:35 +08:00
|
|
|
// When a new private chat message is received, ensure the conversation view is restored.
|
2017-03-03 04:33:49 +08:00
|
|
|
if (op.unreadCounter > 0) {
|
2017-03-22 05:52:10 +08:00
|
|
|
if (_.indexOf(currentClosedChats, op.id) > -1) {
|
2017-03-17 23:27:37 +08:00
|
|
|
Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, op.id));
|
2017-03-02 05:27:05 +08:00
|
|
|
}
|
2017-03-03 04:33:49 +08:00
|
|
|
}
|
2017-03-01 06:40:16 +08:00
|
|
|
|
2017-03-03 05:42:39 +08:00
|
|
|
// Compare openChats with session and push it into filteredChatList
|
|
|
|
// if one of the openChat is not in session.
|
|
|
|
// It will pass to openChats.
|
2017-03-22 05:52:10 +08:00
|
|
|
if (_.indexOf(currentClosedChats, op.id) < 0) {
|
2017-03-03 04:33:49 +08:00
|
|
|
filteredChatList.push(op);
|
2017-03-01 06:40:16 +08:00
|
|
|
}
|
2017-03-03 04:33:49 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
openChats = filteredChatList;
|
2016-06-28 21:10:20 +08:00
|
|
|
|
|
|
|
openChats.push({
|
|
|
|
id: 'public',
|
|
|
|
name: 'Public Chat',
|
2017-03-02 09:03:02 +08:00
|
|
|
icon: 'group_chat',
|
2017-07-13 04:15:50 +08:00
|
|
|
unreadCounter: UnreadMessages.count(PUBLIC_CHAT_USERID),
|
2016-06-28 21:10:20 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return openChats
|
2017-04-13 04:19:39 +08:00
|
|
|
.sort(sortChats);
|
2016-06-28 21:10:20 +08:00
|
|
|
};
|
|
|
|
|
2017-12-09 00:40:52 +08:00
|
|
|
const isVoiceOnlyUser = userId => userId.toString().startsWith('v_');
|
|
|
|
|
2017-08-16 22:56:31 +08:00
|
|
|
const getAvailableActions = (currentUser, user, router, isBreakoutRoom) => {
|
2017-12-09 00:40:52 +08:00
|
|
|
const isDialInUser = isVoiceOnlyUser(user.id) || user.isPhoneUser;
|
2017-12-08 06:07:02 +08:00
|
|
|
|
2017-08-16 22:56:31 +08:00
|
|
|
const hasAuthority = currentUser.isModerator || user.isCurrent;
|
2017-12-08 06:07:02 +08:00
|
|
|
|
|
|
|
const allowedToChatPrivately = !user.isCurrent && !isDialInUser;
|
|
|
|
|
2017-10-31 02:27:17 +08:00
|
|
|
const allowedToMuteAudio = hasAuthority
|
|
|
|
&& user.isVoiceUser
|
|
|
|
&& !user.isMuted
|
|
|
|
&& !user.isListenOnly;
|
2017-12-08 06:07:02 +08:00
|
|
|
|
2017-10-26 20:50:55 +08:00
|
|
|
const allowedToUnmuteAudio = hasAuthority
|
|
|
|
&& user.isVoiceUser
|
2017-10-31 02:27:17 +08:00
|
|
|
&& !user.isListenOnly
|
2017-10-26 20:50:55 +08:00
|
|
|
&& user.isMuted
|
2017-10-31 02:27:17 +08:00
|
|
|
&& (ALLOW_MODERATOR_TO_UNMUTE_AUDIO || user.isCurrent);
|
2017-12-08 06:07:02 +08:00
|
|
|
|
|
|
|
const allowedToResetStatus = hasAuthority
|
|
|
|
&& user.emoji.status !== EMOJI_STATUSES.none
|
|
|
|
&& !isDialInUser;
|
2017-08-16 22:56:31 +08:00
|
|
|
|
2018-01-10 06:28:48 +08:00
|
|
|
// if currentUser is a moderator, allow removing other users
|
|
|
|
const allowedToRemove = currentUser.isModerator && !user.isCurrent && !isBreakoutRoom;
|
2017-08-16 22:56:31 +08:00
|
|
|
|
2017-12-08 06:07:02 +08:00
|
|
|
const allowedToSetPresenter = currentUser.isModerator
|
|
|
|
&& !user.isPresenter
|
|
|
|
&& !isDialInUser;
|
|
|
|
|
|
|
|
const allowedToPromote = currentUser.isModerator
|
|
|
|
&& !user.isCurrent
|
|
|
|
&& !user.isModerator
|
|
|
|
&& !isDialInUser;
|
2017-08-16 22:56:31 +08:00
|
|
|
|
2017-12-08 06:07:02 +08:00
|
|
|
const allowedToDemote = currentUser.isModerator
|
|
|
|
&& !user.isCurrent
|
|
|
|
&& user.isModerator
|
|
|
|
&& !isDialInUser;
|
2017-08-23 02:39:34 +08:00
|
|
|
|
2017-08-16 22:56:31 +08:00
|
|
|
return {
|
|
|
|
allowedToChatPrivately,
|
|
|
|
allowedToMuteAudio,
|
|
|
|
allowedToUnmuteAudio,
|
|
|
|
allowedToResetStatus,
|
2018-01-10 06:28:48 +08:00
|
|
|
allowedToRemove,
|
2017-08-16 22:56:31 +08:00
|
|
|
allowedToSetPresenter,
|
2017-08-23 02:39:34 +08:00
|
|
|
allowedToPromote,
|
|
|
|
allowedToDemote,
|
2017-08-16 22:56:31 +08:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2017-07-12 21:18:26 +08:00
|
|
|
const getCurrentUser = () => {
|
2017-06-03 03:25:02 +08:00
|
|
|
const currentUserId = Auth.userID;
|
2017-07-26 22:09:07 +08:00
|
|
|
const currentUser = Users.findOne({ userId: currentUserId });
|
2016-06-28 21:10:20 +08:00
|
|
|
|
2017-07-26 22:09:07 +08:00
|
|
|
return (currentUser) ? mapUser(currentUser) : null;
|
2016-06-28 21:10:20 +08:00
|
|
|
};
|
|
|
|
|
2017-09-23 01:03:14 +08:00
|
|
|
const normalizeEmojiName = emoji => (
|
2017-11-21 18:50:20 +08:00
|
|
|
emoji in EMOJI_STATUSES ? EMOJI_STATUSES[emoji] : emoji
|
2017-09-23 01:03:14 +08:00
|
|
|
);
|
2017-08-16 22:56:31 +08:00
|
|
|
|
2017-09-07 02:32:29 +08:00
|
|
|
const isMeetingLocked = (id) => {
|
|
|
|
const meeting = Meetings.findOne({ meetingId: id });
|
2017-09-07 01:50:48 +08:00
|
|
|
let isLocked = false;
|
|
|
|
|
2017-09-11 21:42:56 +08:00
|
|
|
if (meeting.lockSettingsProp !== undefined) {
|
2017-09-07 03:57:24 +08:00
|
|
|
const lockSettings = meeting.lockSettingsProp;
|
2017-09-12 03:11:03 +08:00
|
|
|
|
|
|
|
if (lockSettings.disableCam
|
2017-09-22 22:24:24 +08:00
|
|
|
|| lockSettings.disableMic
|
|
|
|
|| lockSettings.disablePrivChat
|
|
|
|
|| lockSettings.disablePubChat) {
|
2017-09-07 03:57:24 +08:00
|
|
|
isLocked = true;
|
|
|
|
}
|
2017-09-01 02:12:53 +08:00
|
|
|
}
|
2017-09-07 01:50:48 +08:00
|
|
|
|
|
|
|
return isLocked;
|
2017-09-12 03:11:03 +08:00
|
|
|
};
|
2017-09-01 02:12:53 +08:00
|
|
|
|
2017-10-03 23:25:56 +08:00
|
|
|
const setEmojiStatus = (userId) => { makeCall('setEmojiStatus', userId, 'none'); };
|
2017-09-29 02:19:57 +08:00
|
|
|
|
2017-10-03 23:25:56 +08:00
|
|
|
const assignPresenter = (userId) => { makeCall('assignPresenter', userId); };
|
2017-09-29 02:19:57 +08:00
|
|
|
|
2018-01-10 06:28:48 +08:00
|
|
|
const removeUser = (userId) => {
|
2017-12-09 00:40:52 +08:00
|
|
|
if (isVoiceOnlyUser(userId)) {
|
|
|
|
makeCall('ejectUserFromVoice', userId);
|
|
|
|
} else {
|
2018-01-10 06:28:48 +08:00
|
|
|
makeCall('removeUser', userId);
|
2017-12-09 00:40:52 +08:00
|
|
|
}
|
|
|
|
};
|
2017-09-29 02:19:57 +08:00
|
|
|
|
2018-02-09 03:00:29 +08:00
|
|
|
const toggleVoice = (userId) => { userId === Auth.userID ? makeCall('toggleSelfVoice') : makeCall('toggleVoice', userId); };
|
2018-02-09 00:06:41 +08:00
|
|
|
|
2017-10-03 23:25:56 +08:00
|
|
|
const changeRole = (userId, role) => { makeCall('changeRole', userId, role); };
|
2017-09-29 02:19:57 +08:00
|
|
|
|
2017-12-06 03:13:11 +08:00
|
|
|
const roving = (event, itemCount, changeState) => {
|
|
|
|
if (this.selectedIndex === undefined) {
|
|
|
|
this.selectedIndex = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.keyCode)) {
|
2017-12-07 01:05:32 +08:00
|
|
|
document.activeElement.blur();
|
2017-12-06 03:13:11 +08:00
|
|
|
this.selectedIndex = -1;
|
|
|
|
changeState(this.selectedIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.keyCode === KEY_CODES.ARROW_DOWN) {
|
|
|
|
this.selectedIndex += 1;
|
|
|
|
|
|
|
|
if (this.selectedIndex === itemCount) {
|
|
|
|
this.selectedIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
changeState(this.selectedIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.keyCode === KEY_CODES.ARROW_UP) {
|
|
|
|
this.selectedIndex -= 1;
|
|
|
|
|
|
|
|
if (this.selectedIndex < 0) {
|
|
|
|
this.selectedIndex = itemCount - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
changeState(this.selectedIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([KEY_CODES.ARROW_RIGHT, KEY_CODES.SPACE].includes(event.keyCode)) {
|
|
|
|
document.activeElement.firstChild.click();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-01 04:25:42 +08:00
|
|
|
export default {
|
2017-09-29 02:19:57 +08:00
|
|
|
setEmojiStatus,
|
|
|
|
assignPresenter,
|
2018-01-10 06:28:48 +08:00
|
|
|
removeUser,
|
2017-09-29 02:19:57 +08:00
|
|
|
toggleVoice,
|
|
|
|
changeRole,
|
2016-06-07 00:45:30 +08:00
|
|
|
getUsers,
|
2016-06-28 21:10:20 +08:00
|
|
|
getOpenChats,
|
|
|
|
getCurrentUser,
|
2017-08-16 22:56:31 +08:00
|
|
|
getAvailableActions,
|
|
|
|
normalizeEmojiName,
|
2017-09-12 03:11:03 +08:00
|
|
|
isMeetingLocked,
|
2017-09-22 22:24:24 +08:00
|
|
|
isPublicChat,
|
2017-12-06 03:13:11 +08:00
|
|
|
roving,
|
2018-03-21 03:35:28 +08:00
|
|
|
setCustomLogoUrl,
|
|
|
|
getCustomLogoUrl,
|
2016-06-01 04:25:42 +08:00
|
|
|
};
|