feat(client): getChatMember method and ChatMember class

This commit is contained in:
teidesu 2021-04-11 14:56:34 +03:00
parent 82a9ec80ea
commit 89dafa570b
6 changed files with 352 additions and 1 deletions

View file

@ -21,6 +21,7 @@ import { createSupergroup } from './methods/chats/create-supergroup'
import { deleteChannel } from './methods/chats/delete-channel' import { deleteChannel } from './methods/chats/delete-channel'
import { deleteGroup } from './methods/chats/delete-group' import { deleteGroup } from './methods/chats/delete-group'
import { deleteHistory } from './methods/chats/delete-history' import { deleteHistory } from './methods/chats/delete-history'
import { getChatMember } from './methods/chats/get-chat-member'
import { getChatPreview } from './methods/chats/get-chat-preview' import { getChatPreview } from './methods/chats/get-chat-preview'
import { getChat } from './methods/chats/get-chat' import { getChat } from './methods/chats/get-chat'
import { getFullChat } from './methods/chats/get-full-chat' import { getFullChat } from './methods/chats/get-full-chat'
@ -69,6 +70,7 @@ import { IMessageEntityParser } from './parser'
import { Readable } from 'stream' import { Readable } from 'stream'
import { import {
Chat, Chat,
ChatMember,
ChatPreview, ChatPreview,
FileDownloadParameters, FileDownloadParameters,
InputFileLike, InputFileLike,
@ -444,6 +446,19 @@ export class TelegramClient extends BaseTelegramClient {
): Promise<void> { ): Promise<void> {
return deleteHistory.apply(this, arguments) return deleteHistory.apply(this, arguments)
} }
/**
* Get information about a single chat member
*
* @param chatId Chat ID or username
* @param userId User ID, username, phone number, `"me"` or `"self"`
* @throws MtCuteNotFoundError In case given user is not a participant of a given chat
*/
getChatMember(
chatId: InputPeerLike,
userId: InputPeerLike
): Promise<ChatMember> {
return getChatMember.apply(this, arguments)
}
/** /**
* Get preview information about a private chat. * Get preview information about a private chat.
* *

View file

@ -11,6 +11,7 @@ import {
User, User,
Chat, Chat,
ChatPreview, ChatPreview,
ChatMember,
TermsOfService, TermsOfService,
SentCode, SentCode,
MaybeDynamic, MaybeDynamic,

View file

@ -0,0 +1,78 @@
import { TelegramClient } from '../../client'
import {
InputPeerLike,
MtCuteInvalidPeerTypeError,
} from '../../types'
import {
createUsersChatsIndex,
normalizeToInputChannel,
normalizeToInputPeer,
} from '../../utils/peer-utils'
import { assertTypeIs } from '../../utils/type-assertion'
import { tl } from '@mtcute/tl'
import { ChatMember } from '../../types'
import { UserNotParticipantError } from '@mtcute/tl/errors'
/**
* Get information about a single chat member
*
* @param chatId Chat ID or username
* @param userId User ID, username, phone number, `"me"` or `"self"`
* @throws UserNotParticipantError In case given user is not a participant of a given chat
* @internal
*/
export async function getChatMember(
this: TelegramClient,
chatId: InputPeerLike,
userId: InputPeerLike
): Promise<ChatMember> {
const user = normalizeToInputPeer(await this.resolvePeer(userId))
const chat = await this.resolvePeer(chatId)
const chatInput = normalizeToInputPeer(chat)
if (chatInput._ === 'inputPeerChat') {
const res = await this.call({
_: 'messages.getFullChat',
chatId: chatInput.chatId,
})
assertTypeIs(
'getChatMember (@ messages.getFullChat)',
res.fullChat,
'chatFull'
)
const members =
res.fullChat.participants._ === 'chatParticipantsForbidden'
? []
: res.fullChat.participants.participants
const { users } = createUsersChatsIndex(res)
for (const m of members) {
if (
(user._ === 'inputPeerSelf' &&
(users[m.userId] as tl.RawUser).self) ||
(user._ === 'inputPeerUser' && m.userId === user.userId)
) {
return new ChatMember(this, m, users)
}
}
throw new UserNotParticipantError()
} else if (chatInput._ === 'inputPeerChannel') {
const res = await this.call({
_: 'channels.getParticipant',
channel: normalizeToInputChannel(chat)!,
participant: user,
})
const { users } = createUsersChatsIndex(res)
return new ChatMember(
this,
res.participant,
users
)
} else throw new MtCuteInvalidPeerTypeError(chatId, 'chat or channel')
}

View file

@ -0,0 +1,253 @@
import { makeInspectable } from '../utils'
import { TelegramClient } from '../../client'
import { tl } from '@mtcute/tl'
import { User } from './user'
import { assertTypeIs } from '../../utils/type-assertion'
import { ChatPermissions } from './chat-permissions'
export namespace ChatMember {
/**
* Status of the member:
* - `creator`: user is the creator of the chat
* - `admin`: user has admin rights in the chat
* - `member`: user is a normal member of the chat
* - `restricted`: user has some restrictions applied
* - `banned`: user was banned from the chat
* - `left`: user left the chat on their own
*/
export type Status =
| 'creator'
| 'admin'
| 'member'
| 'restricted'
| 'banned'
| 'left'
}
/**
* Information about one chat member
*/
export class ChatMember {
readonly client: TelegramClient
readonly raw: tl.TypeChatParticipant | tl.TypeChannelParticipant
/** Map of users in this object. Mainly for internal use */
readonly _users: Record<number, tl.TypeUser>
constructor(
client: TelegramClient,
raw: tl.TypeChatParticipant | tl.TypeChannelParticipant,
users: Record<number, tl.TypeUser>,
) {
this.client = client
this.raw = raw
this._users = users
}
private _user?: User
/**
* Information about the user
*/
get user(): User {
if (this._user === undefined) {
if (
this.raw._ === 'channelParticipantBanned' ||
this.raw._ === 'channelParticipantLeft'
) {
assertTypeIs(
'ChatMember#user (raw.peer)',
this.raw.peer,
'peerUser'
)
this._user = new User(
this.client,
this._users[this.raw.peer.userId] as tl.RawUser
)
} else {
this._user = new User(
this.client,
this._users[this.raw.userId] as tl.RawUser
)
}
}
return this._user
}
/**
* Get the chat member status
*/
get status(): ChatMember.Status {
if (
this.raw._ === 'channelParticipant' ||
this.raw._ === 'channelParticipantSelf' ||
this.raw._ === 'chatParticipant'
) {
return 'member'
}
if (
this.raw._ === 'channelParticipantCreator' ||
this.raw._ === 'chatParticipantCreator'
) {
return 'creator'
}
if (
this.raw._ === 'channelParticipantAdmin' ||
this.raw._ === 'chatParticipantAdmin'
) {
return 'admin'
}
if (this.raw._ === 'channelParticipantLeft') {
return 'left'
}
if (this.raw._ === 'channelParticipantBanned') {
return this.raw.bannedRights.viewMessages ? 'banned' : 'restricted'
}
// fallback
return 'member'
}
/**
* Custom title (for creators and admins).
*
* `null` for non-admins and in case custom title is not set.
*/
get title(): string | null {
return this.raw._ === 'channelParticipantCreator' ||
this.raw._ === 'channelParticipantAdmin'
? this.raw.rank ?? null
: null
}
/**
* Date when the user has joined the chat.
*
* Not available for creators and left members
*/
get joinedDate(): Date | null {
return this.raw._ === 'channelParticipantCreator' ||
this.raw._ === 'chatParticipantCreator' ||
this.raw._ === 'channelParticipantLeft'
? null
: new Date(this.raw.date * 1000)
}
private _invitedBy?: User | null
/**
* Information about whoever invited this member to the chat.
*
* Only available in the following cases:
* - `user` is yourself
* - `chat` is a legacy group
* - `chat` is a supergroup/channel, and `user` is an admin
*/
get invitedBy(): User | null {
if (this._invitedBy === undefined) {
if (
this.raw._ !== 'chatParticipantCreator' &&
this.raw._ !== 'channelParticipantCreator' &&
this.raw._ !== 'channelParticipant' &&
this.raw._ !== 'channelParticipantBanned' &&
this.raw._ !== 'channelParticipantLeft' &&
this.raw.inviterId
) {
this._invitedBy = new User(
this.client,
this._users[this.raw.inviterId] as tl.RawUser
)
} else {
this._invitedBy = null
}
}
return this._invitedBy
}
private _promotedBy?: User | null
/**
* Information about whoever promoted this admin.
*
* Only available if `status = admin`.
*/
get promotedBy(): User | null {
if (this._promotedBy === undefined) {
if (this.raw._ === 'channelParticipantAdmin') {
this._promotedBy = new User(
this.client,
this._users[this.raw.promotedBy] as tl.RawUser
)
} else {
this._promotedBy = null
}
}
return this._promotedBy
}
private _restrictedBy?: User | null
/**
* Information about whoever restricted this user.
*
* Only available if `status = restricted or status = banned`
*/
get restrictedBy(): User | null {
if (this._restrictedBy === undefined) {
if (this.raw._ === 'channelParticipantBanned') {
this._restrictedBy = new User(
this.client,
this._users[this.raw.kickedBy] as tl.RawUser
)
} else {
this._restrictedBy = null
}
}
return this._restrictedBy
}
private _restrictions?: ChatPermissions
/**
* For restricted and banned users,
* information about the restrictions
*/
get restrictions(): ChatPermissions | null {
if (this.raw._ !== 'channelParticipantBanned') return null
if (!this._restrictions) {
this._restrictions = new ChatPermissions(this.raw.bannedRights)
}
return this._restrictions
}
/**
* Whether this member is a part of the chat now.
*
* Makes sense only when `status = restricted or staus = banned`
*/
get isMember(): boolean {
return this.raw._ === 'channelParticipantBanned'
? !this.raw.left
: this.raw._ !== 'channelParticipantLeft'
}
/**
* For admins and creator of supergroup/channels,
* list of their admin permissions.
*
* Also contains whether this admin is anonymous.
*/
get permissions(): tl.RawChatAdminRights | null {
return this.raw._ === 'channelParticipantAdmin' ||
this.raw._ === 'channelParticipantCreator'
? this.raw.adminRights
: null
}
}
makeInspectable(ChatMember)

View file

@ -2,7 +2,7 @@ import { tl } from '@mtcute/tl'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
/** /**
* Represents the rights of a normal user in a {@link Chat}. * Represents the permissions of a user in a {@link Chat}.
*/ */
export class ChatPermissions { export class ChatPermissions {
readonly _bannedRights: tl.RawChatBannedRights readonly _bannedRights: tl.RawChatBannedRights
@ -115,6 +115,9 @@ export class ChatPermissions {
/** /**
* UNIX date until which these permissions are valid, * UNIX date until which these permissions are valid,
* or `null` if forever. * or `null` if forever.
*
* For example, represents the time when the restrictions
* will be lifted from a {@link ChatMember}
*/ */
get untilDate(): Date | null { get untilDate(): Date | null {
return this._bannedRights.untilDate === 0 return this._bannedRights.untilDate === 0

View file

@ -3,6 +3,7 @@ import { tl } from '@mtcute/tl'
export * from './user' export * from './user'
export * from './chat' export * from './chat'
export * from './chat-preview' export * from './chat-preview'
export * from './chat-member'
/** /**
* Peer types that have one-to-one relation to tl.Peer* types. * Peer types that have one-to-one relation to tl.Peer* types.