diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index b9ec3fd6..18db07c2 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -27,6 +27,7 @@ import { deleteChatPhoto } from './methods/chats/delete-chat-photo' import { deleteGroup } from './methods/chats/delete-group' import { deleteHistory } from './methods/chats/delete-history' import { deleteUserHistory } from './methods/chats/delete-user-history' +import { getChatEventLog } from './methods/chats/get-chat-event-log' import { getChatMember } from './methods/chats/get-chat-member' import { getChatMembers } from './methods/chats/get-chat-members' import { getChatPreview } from './methods/chats/get-chat-preview' @@ -137,6 +138,8 @@ import { IMessageEntityParser } from './parser' import { Readable } from 'stream' import { Chat, + ChatEvent, + ChatInviteLink, ChatMember, ChatPreview, Dialog, @@ -673,6 +676,74 @@ export interface TelegramClient extends BaseTelegramClient { chatId: InputPeerLike, userId: InputPeerLike ): Promise + /** + * Get chat event log ("Recent actions" in official + * clients). + * + * Only available for supergroups and channels, and + * requires (any) administrator rights. + * + * Results are returned in reverse chronological + * order (i.e. newest first) and event IDs are + * in direct chronological order (i.e. newer + * events have bigger event ID) + * + * @param chatId Chat ID + * @param params + */ + getChatEventLog( + chatId: InputPeerLike, + params?: { + /** + * Search query + */ + query?: string + + /** + * Minimum event ID to return + */ + minId?: tl.Long + + /** + * Maximum event ID to return, + * can be used as a base offset + */ + maxId?: tl.Long + + /** + * List of users whose actions to return + */ + users?: InputPeerLike[] + + /** + * Event filters. Can be a TL object, or one or more + * action types. + * + * Note that some filters are grouped in TL + * (i.e. `info=true` will return `title_changed`, + * `username_changed` and many more), + * and when passing one or more action types, + * they will be filtered locally. + */ + filters?: + | tl.TypeChannelAdminLogEventsFilter + | MaybeArray['type']> + + /** + * Limit the number of events returned. + * + * Defaults to `Infinity`, i.e. all events are returned + */ + limit?: number + + /** + * Chunk size, usually not needed. + * + * Defaults to `100` + */ + chunkSize?: number + } + ): AsyncIterableIterator /** * Get information about a single chat member * @@ -2682,6 +2753,7 @@ export class TelegramClient extends BaseTelegramClient { deleteGroup = deleteGroup deleteHistory = deleteHistory deleteUserHistory = deleteUserHistory + getChatEventLog = getChatEventLog getChatMember = getChatMember getChatMembers = getChatMembers getChatPreview = getChatPreview diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index 3d8a7741..a1606aca 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -33,7 +33,9 @@ import { StickerSet, Poll, TypingStatus, - Photo + Photo, + ChatEvent, + ChatInviteLink } from '../types' // @copy diff --git a/packages/client/src/methods/chats/get-chat-event-log.ts b/packages/client/src/methods/chats/get-chat-event-log.ts new file mode 100644 index 00000000..c4bd8463 --- /dev/null +++ b/packages/client/src/methods/chats/get-chat-event-log.ts @@ -0,0 +1,242 @@ +import { TelegramClient } from '../../client' +import { + InputPeerLike, + MtCuteInvalidPeerTypeError, + ChatEvent, +} from '../../types' +import { tl } from '@mtcute/tl' +import { MaybeArray } from '@mtcute/core' +import bigInt from 'big-integer' +import { + createUsersChatsIndex, + normalizeToInputChannel, + normalizeToInputUser, +} from '../../utils/peer-utils' + +/** + * Get chat event log ("Recent actions" in official + * clients). + * + * Only available for supergroups and channels, and + * requires (any) administrator rights. + * + * Results are returned in reverse chronological + * order (i.e. newest first) and event IDs are + * in direct chronological order (i.e. newer + * events have bigger event ID) + * + * @param chatId Chat ID + * @param params + * @internal + */ +export async function* getChatEventLog( + this: TelegramClient, + chatId: InputPeerLike, + params?: { + /** + * Search query + */ + query?: string + + /** + * Minimum event ID to return + */ + minId?: tl.Long + + /** + * Maximum event ID to return, + * can be used as a base offset + */ + maxId?: tl.Long + + /** + * List of users whose actions to return + */ + users?: InputPeerLike[] + + /** + * Event filters. Can be a TL object, or one or more + * action types. + * + * Note that some filters are grouped in TL + * (i.e. `info=true` will return `title_changed`, + * `username_changed` and many more), + * and when passing one or more action types, + * they will be filtered locally. + */ + filters?: + | tl.TypeChannelAdminLogEventsFilter + | MaybeArray['type']> + + /** + * Limit the number of events returned. + * + * Defaults to `Infinity`, i.e. all events are returned + */ + limit?: number + + /** + * Chunk size, usually not needed. + * + * Defaults to `100` + */ + chunkSize?: number + } +): AsyncIterableIterator { + if (!params) params = {} + + const channel = normalizeToInputChannel(await this.resolvePeer(chatId)) + if (!channel) throw new MtCuteInvalidPeerTypeError(chatId, 'channel') + + let current = 0 + let maxId = params.maxId ?? bigInt.zero + const minId = params.minId ?? bigInt.zero + const query = params.query ?? '' + + const total = params.limit || Infinity + const chunkSize = Math.min(params.chunkSize ?? 100, total) + + const admins: tl.TypeInputUser[] | undefined = params.users + ? ((await Promise.all( + params.users + .map((u) => this.resolvePeer(u).then(normalizeToInputUser)) + .filter(Boolean) + )) as tl.TypeInputUser[]) + : undefined + + let serverFilter: + | tl.Mutable + | undefined = undefined + let localFilter: Record | undefined = undefined + if (params.filters) { + if ( + typeof params.filters === 'string' || + Array.isArray(params.filters) + ) { + let input = params.filters + if (!Array.isArray(input)) input = [input] + + serverFilter = { + _: 'channelAdminLogEventsFilter', + } + localFilter = {} + + input.forEach((type) => { + localFilter![type] = true + switch (type) { + case 'user_joined': + serverFilter!.join = true + break + case 'user_left': + serverFilter!.leave = true + break + case 'user_invited': + serverFilter!.invite = true + break + case 'title_changed': + case 'description_changed': + case 'linked_chat_changed': + case 'location_changed': + case 'photo_changed': + case 'username_changed': + case 'stickerset_changed': + serverFilter!.info = true + break + case 'invites_toggled': + case 'history_toggled': + case 'signatures_toggled': + case 'def_perms_changed': + serverFilter!.settings = true + break + case 'msg_pinned': + serverFilter!.pinned = true + break + case 'msg_edited': + case 'poll_stopped': + serverFilter!.edit = true + break + case 'msg_deleted': + serverFilter!.delete = true + break + case 'user_perms_changed': + serverFilter!.ban = true + serverFilter!.unban = true + serverFilter!.kick = true + serverFilter!.unkick = true + break + case 'user_admin_perms_changed': + serverFilter!.promote = true + serverFilter!.demote = true + break + case 'slow_mode_changed': + case 'ttl_changed': + // not documented so idk, enable both + serverFilter!.settings = true + serverFilter!.info = true + break + case 'call_started': + case 'call_ended': + serverFilter!.groupCall = true + break + case 'call_setting_changed': + // not documented so idk, enable all + serverFilter!.groupCall = true + serverFilter!.settings = true + serverFilter!.info = true + break + case 'user_joined_invite': + // not documented so idk, enable all + serverFilter!.join = true + serverFilter!.invite = true + serverFilter!.invites = true + break + case 'invite_deleted': + case 'invite_edited': + case 'invite_revoked': + serverFilter!.invites = true + break + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _: never = type + } + } + }) + } else { + serverFilter = params.filters + } + } + + for (;;) { + const res = await this.call({ + _: 'channels.getAdminLog', + channel, + q: query, + eventsFilter: serverFilter, + admins, + maxId, + minId, + limit: Math.min(chunkSize, total - current), + }) + + if (!res.events.length) break + + const { users, chats } = createUsersChatsIndex(res) + const last = res.events[res.events.length - 1] + maxId = last.id + + for (const evt of res.events) { + const parsed = new ChatEvent(this, evt, users, chats) + + if ( + localFilter && + (!parsed.action || !localFilter[parsed.action.type]) + ) + continue + + current += 1 + yield parsed + + if (current >= total) break + } + } +} diff --git a/packages/client/src/types/messages/message.ts b/packages/client/src/types/messages/message.ts index 517a6b05..f1537025 100644 --- a/packages/client/src/types/messages/message.ts +++ b/packages/client/src/types/messages/message.ts @@ -563,7 +563,7 @@ export class Message { * * @param revoke Whether to "revoke" (i.e. delete for both sides). Only used for chats and private chats. */ - delete(revoke = false): Promise { + delete(revoke = false): Promise { return this.client.deleteMessages(this.chat.inputPeer, this.id, revoke) } diff --git a/packages/client/src/types/peers/chat-event.ts b/packages/client/src/types/peers/chat-event.ts new file mode 100644 index 00000000..61997010 --- /dev/null +++ b/packages/client/src/types/peers/chat-event.ts @@ -0,0 +1,553 @@ +import { makeInspectable } from '../utils' +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { User } from './user' +import { ChatMember } from './chat-member' +import { Photo } from '../media' +import { Message } from '../messages' +import { ChatPermissions } from './chat-permissions' +import { ChatLocation } from './chat-location' +import { ChatInviteLink } from './chat-invite-link' + +export namespace ChatEvent { + /** A user has joined the group (in the case of big groups, info of the user that has joined isn't shown) */ + export interface ActionUserJoined { + type: 'user_joined' + } + + /** A user has joined the group using an invite link */ + export interface ActionUserJoinedInvite { + type: 'user_joined_invite' + + /** Invite link user to join */ + link: ChatInviteLink + } + + /** A user has left the group (in the case of big groups, info of the user that has joined isn't shown) */ + export interface ActionUserLeft { + type: 'user_left' + } + + /** A user was invited to the group */ + export interface ActionUserInvited { + type: 'user_invited' + + /** Member who has been invited */ + member: ChatMember + } + + /** Group title has been changed */ + export interface ActionTitleChanged { + type: 'title_changed' + + /** Old chat title */ + old: string + + /** New chat title */ + new: string + } + + /** Group description has been changed */ + export interface ActionDescriptionChanged { + type: 'description_changed' + + /** Old description */ + old: string + + /** New description */ + new: string + } + + /** Group username has been changed */ + export interface ActionUsernameChanged { + type: 'username_changed' + + /** Old username */ + old: string + + /** New username */ + new: string + } + + /** Group photo has been changed */ + export interface ActionPhotoChanged { + type: 'photo_changed' + + /** Old photo */ + old: Photo + + /** New photo */ + new: Photo + } + + /** Invites were enabled/disabled */ + export interface ActionInvitesToggled { + type: 'invites_toggled' + + /** Old value */ + old: boolean + + /** New value */ + new: boolean + } + + /** Signatures were enabled/disabled */ + export interface ActionSignaturesToggled { + type: 'signatures_toggled' + + /** Old value */ + old: boolean + + /** New value */ + new: boolean + } + + /** A message has been pinned */ + export interface ActionMessagePinned { + type: 'msg_pinned' + + /** Message which was pinned */ + message: Message + } + + /** A message has been edited */ + export interface ActionMessageEdited { + type: 'msg_edited' + + /** Old message */ + old: Message + + /** New message */ + new: Message + } + + /** A message has been deleted */ + export interface ActionMessageDeleted { + type: 'msg_deleted' + + /** Message which was deleted */ + message: Message + } + + /** User's permissions were changed */ + export interface ActionUserPermissionsChanged { + type: 'user_perms_changed' + + /** Information about member before change */ + old: ChatMember + + /** Information about member after change */ + new: ChatMember + } + + /** User's admin permissions were changed */ + export interface ActionUserAdminPermissionsChanged { + type: 'user_admin_perms_changed' + + /** Information about member before change */ + old: ChatMember + + /** Information about member after change */ + new: ChatMember + } + + /** Group stickerset has been changed */ + export interface ActionStickersetChanged { + type: 'stickerset_changed' + + /** Old stickerset */ + old: tl.TypeInputStickerSet + + /** New stickerset */ + new: tl.TypeInputStickerSet + } + + /** History visibility for new users has been toggled */ + export interface ActionHistoryToggled { + type: 'history_toggled' + + /** Old value (`false` if new users can see history) */ + old: boolean + + /** New value (`false` if new users can see history) */ + new: boolean + } + + /** Group default permissions have been changed */ + export interface ActionDefaultPermissionsChanged { + type: 'def_perms_changed' + + /** Old default permissions */ + old: ChatPermissions + + /** New default permissions */ + new: ChatPermissions + } + + /** Poll has been stopped */ + export interface ActionPollStopped { + type: 'poll_stopped' + + /** Message containing the poll */ + message: Message + } + + /** Linked chat has been changed */ + export interface ActionLinkedChatChanged { + type: 'linked_chat_changed' + + /** ID of the old linked chat */ + old: number + + /** ID of the new linked chat */ + new: number + } + + /** Group location has been changed */ + export interface ActionLocationChanged { + type: 'location_changed' + + /** Old location */ + old: ChatLocation | null + + /** New location */ + new: ChatLocation | null + } + + /** Group slow mode delay has been changed */ + export interface ActionSlowModeChanged { + type: 'slow_mode_changed' + + /** Old delay (can be 0) */ + old: number + + /** New delay (can be 0) */ + new: number + } + + /** Group call has been started */ + export interface ActionCallStarted { + type: 'call_started' + + /** TL object representing the call */ + call: tl.TypeInputGroupCall + } + + /** Group call has ended */ + export interface ActionCallEnded { + type: 'call_ended' + + /** TL object representing the call */ + call: tl.TypeInputGroupCall + } + + /** Group call "join muted" setting has been changed */ + export interface ActionCallSettingChanged { + type: 'call_setting_changed' + + /** Whether new call participants should join muted */ + joinMuted: boolean + } + + /** Invite link has been deleted */ + export interface ActionInviteLinkDeleted { + type: 'invite_deleted' + + /** Invite link which was deleted */ + link: ChatInviteLink + } + + /** Invite link has been edited */ + export interface ActionInviteLinkEdited { + type: 'invite_edited' + + /** Old invite link */ + old: ChatInviteLink + + /** New invite link */ + new: ChatInviteLink + } + + /** Invite link has been revoked */ + export interface ActionInviteLinkRevoked { + type: 'invite_revoked' + + /** Invite link which was revoked */ + link: ChatInviteLink + } + + /** History TTL has been changed */ + export interface ActionTtlChanged { + type: 'ttl_changed' + + /** Old TTL value (can be 0) */ + old: number + + /** New TTL value (can be 0) */ + new: number + } + + /** Chat event action (`null` if unsupported) */ + export type Action = + | ActionUserJoined + | ActionUserLeft + | ActionUserInvited + | ActionTitleChanged + | ActionDescriptionChanged + | ActionUsernameChanged + | ActionPhotoChanged + | ActionInvitesToggled + | ActionSignaturesToggled + | ActionMessagePinned + | ActionMessageEdited + | ActionMessageDeleted + | ActionUserPermissionsChanged + | ActionUserAdminPermissionsChanged + | ActionStickersetChanged + | ActionHistoryToggled + | ActionDefaultPermissionsChanged + | ActionPollStopped + | ActionLinkedChatChanged + | ActionLocationChanged + | ActionSlowModeChanged + | ActionCallStarted + | ActionCallEnded + | ActionCallSettingChanged + | ActionUserJoinedInvite + | ActionInviteLinkDeleted + | ActionInviteLinkEdited + | ActionInviteLinkRevoked + | ActionTtlChanged + | null +} + +function _actionFromTl( + this: ChatEvent, + e: tl.TypeChannelAdminLogEventAction +): ChatEvent.Action { + switch (e._) { + case 'channelAdminLogEventActionParticipantJoin': + return { type: 'user_joined' } + case 'channelAdminLogEventActionChangeTitle': + return { + type: 'title_changed', + old: e.prevValue, + new: e.newValue, + } + case 'channelAdminLogEventActionChangeAbout': + return { + type: 'description_changed', + old: e.prevValue, + new: e.newValue, + } + case 'channelAdminLogEventActionChangeUsername': + return { + type: 'username_changed', + old: e.prevValue, + new: e.newValue, + } + case 'channelAdminLogEventActionChangePhoto': + return { + type: 'photo_changed', + old: new Photo(this.client, e.prevPhoto as tl.RawPhoto), + new: new Photo(this.client, e.newPhoto as tl.RawPhoto) + } + case 'channelAdminLogEventActionToggleInvites': + return { + type: 'invites_toggled', + old: !e.newValue, + new: e.newValue + } + case 'channelAdminLogEventActionToggleSignatures': + return { + type: 'signatures_toggled', + old: !e.newValue, + new: e.newValue + } + case 'channelAdminLogEventActionUpdatePinned': + return { + type: 'msg_pinned', + message: new Message(this.client, e.message, this._users, this._chats) + } + case 'channelAdminLogEventActionEditMessage': + return { + type: 'msg_edited', + old: new Message(this.client, e.prevMessage, this._users, this._chats), + new: new Message(this.client, e.newMessage, this._users, this._chats) + } + case 'channelAdminLogEventActionDeleteMessage': + return { + type: 'msg_deleted', + message: new Message(this.client, e.message, this._users, this._chats) + } + case 'channelAdminLogEventActionParticipantLeave': + return { type: 'user_left' } + case 'channelAdminLogEventActionParticipantInvite': + return { + type: 'user_invited', + member: new ChatMember(this.client, e.participant, this._users), + } + case 'channelAdminLogEventActionParticipantToggleBan': + return { + type: 'user_perms_changed', + old: new ChatMember(this.client, e.prevParticipant, this._users), + new: new ChatMember(this.client, e.newParticipant, this._users) + } + case 'channelAdminLogEventActionParticipantToggleAdmin': + return { + type: 'user_admin_perms_changed', + old: new ChatMember(this.client, e.prevParticipant, this._users), + new: new ChatMember(this.client, e.newParticipant, this._users) + } + case 'channelAdminLogEventActionChangeStickerSet': + return { + type: 'stickerset_changed', + old: e.prevStickerset, + new: e.newStickerset + } + case 'channelAdminLogEventActionTogglePreHistoryHidden': + return { + type: 'history_toggled', + old: !e.newValue, + new: e.newValue + } + case 'channelAdminLogEventActionDefaultBannedRights': + return { + type: 'def_perms_changed', + old: new ChatPermissions(e.prevBannedRights), + new: new ChatPermissions(e.newBannedRights) + } + case 'channelAdminLogEventActionStopPoll': + return { + type: 'poll_stopped', + message: new Message(this.client, e.message, this._users, this._chats) + } + case 'channelAdminLogEventActionChangeLinkedChat': + return { + type: 'linked_chat_changed', + old: e.prevValue, + new: e.newValue + } + case 'channelAdminLogEventActionChangeLocation': + return { + type: 'location_changed', + old: e.prevValue._ === 'channelLocationEmpty' ? null : new ChatLocation(this.client, e.prevValue), + new: e.newValue._ === 'channelLocationEmpty' ? null : new ChatLocation(this.client, e.newValue), + } + case 'channelAdminLogEventActionToggleSlowMode': + return { + type: 'slow_mode_changed', + old: e.prevValue, + new: e.newValue + } + case 'channelAdminLogEventActionStartGroupCall': + return { + type: 'call_started', + call: e.call + } + case 'channelAdminLogEventActionDiscardGroupCall': + return { + type: 'call_ended', + call: e.call + } + case 'channelAdminLogEventActionParticipantMute': + case 'channelAdminLogEventActionParticipantUnmute': + case 'channelAdminLogEventActionParticipantVolume': + // todo + return null + case 'channelAdminLogEventActionToggleGroupCallSetting': + return { + type: 'call_setting_changed', + joinMuted: e.joinMuted + } + case 'channelAdminLogEventActionParticipantJoinByInvite': + return { + type: 'user_joined_invite', + link: new ChatInviteLink(this.client, e.invite, this._users) + } + case 'channelAdminLogEventActionExportedInviteDelete': + return { + type: 'invite_deleted', + link: new ChatInviteLink(this.client, e.invite, this._users) + } + case 'channelAdminLogEventActionExportedInviteRevoke': + return { + type: 'invite_revoked', + link: new ChatInviteLink(this.client, e.invite, this._users) + } + case 'channelAdminLogEventActionExportedInviteEdit': + return { + type: 'invite_edited', + old: new ChatInviteLink(this.client, e.prevInvite, this._users), + new: new ChatInviteLink(this.client, e.newInvite, this._users) + } + case 'channelAdminLogEventActionChangeHistoryTTL': + return { + type: 'ttl_changed', + old: e.prevValue, + new: e.newValue + } + default: + return null + } +} + +export class ChatEvent { + readonly client: TelegramClient + readonly raw: tl.TypeChannelAdminLogEvent + + readonly _users: Record + readonly _chats: Record + + constructor( + client: TelegramClient, + raw: tl.TypeChannelAdminLogEvent, + users: Record, + chats: Record + ) { + this.client = client + this.raw = raw + this._users = users + this._chats = chats + } + + /** + * Event ID. + * + * Event IDs are generated in direct chronological order + * (i.e. newer events have bigger event ID) + */ + get id(): tl.Long { + return this.raw.id + } + + /** + * Date of the event + */ + get date(): Date { + return new Date(this.raw.date * 1000) + } + + private _actor?: User + /** + * Actor of the event + */ + get actor(): User { + if (!this._actor) { + this._actor = new User(this.client, this._users[this.raw.userId]) + } + + return this._actor + } + + private _action?: ChatEvent.Action + get action(): ChatEvent.Action { + if (!this._action) { + this._action = _actionFromTl.call(this, this.raw.action) + } + + return this._action! + } +} + +makeInspectable(ChatEvent) diff --git a/packages/client/src/types/peers/chat-location.ts b/packages/client/src/types/peers/chat-location.ts new file mode 100644 index 00000000..78aa1e3a --- /dev/null +++ b/packages/client/src/types/peers/chat-location.ts @@ -0,0 +1,38 @@ +import { TelegramClient } from '../../client' +import { tl } from '@mtcute/tl' +import { Location } from '../media' +import { makeInspectable } from '../utils' + +/** + * Geolocation of a supergroup + */ +export class ChatLocation { + readonly client: TelegramClient + readonly raw: tl.RawChannelLocation + + constructor (client: TelegramClient, raw: tl.RawChannelLocation) { + this.client = client + this.raw = raw + } + + private _location?: Location + /** + * Location of the chat + */ + get location(): Location { + if (!this._location) { + this._location = new Location(this.client, this.raw.geoPoint as tl.RawGeoPoint) + } + + return this._location + } + + /** + * Textual description of the address + */ + get address(): string { + return this.raw.address + } +} + +makeInspectable(ChatLocation) diff --git a/packages/client/src/types/peers/chat.ts b/packages/client/src/types/peers/chat.ts index 88841aa0..467ec9db 100644 --- a/packages/client/src/types/peers/chat.ts +++ b/packages/client/src/types/peers/chat.ts @@ -6,6 +6,7 @@ import { getMarkedPeerId, MaybeArray } from '@mtcute/core' import { MtCuteArgumentError, MtCuteTypeAssertionError } from '../errors' import { makeInspectable } from '../utils' import { InputPeerLike, User } from './index' +import { ChatLocation } from './chat-location' export namespace Chat { /** @@ -409,6 +410,22 @@ export class Chat { */ readonly distance?: number + private _location?: ChatLocation + /** + * Location of the chat. + * Returned only in {@link TelegramClient.getFullChat} + */ + get location(): ChatLocation | null { + if (!this.fullPeer || this.fullPeer._ !== 'channelFull' || this.fullPeer.location?._ !== 'channelLocation') + return null + + if (!this._location) { + this._location = new ChatLocation(this.client, this.fullPeer.location) + } + + return this._location + } + private _linkedChat?: Chat /** * The linked discussion group (in case of channels) diff --git a/packages/client/src/types/peers/index.ts b/packages/client/src/types/peers/index.ts index 48384cd9..9ca0d076 100644 --- a/packages/client/src/types/peers/index.ts +++ b/packages/client/src/types/peers/index.ts @@ -5,6 +5,7 @@ export * from './chat' export * from './chat-preview' export { InputChatPermissions } from './chat-permissions' export * from './chat-member' +export * from './chat-event' export * from './chat-invite-link' export * from './typing-status'