feat(client): chats and chat joining related methods, bound methods and classes

This commit is contained in:
teidesu 2021-04-10 17:11:25 +03:00
parent 6911d7d756
commit 383f133292
10 changed files with 396 additions and 2 deletions

View file

@ -13,6 +13,10 @@ import { signInBot } from './methods/auth/sign-in-bot'
import { signIn } from './methods/auth/sign-in' import { signIn } from './methods/auth/sign-in'
import { signUp } from './methods/auth/sign-up' import { signUp } from './methods/auth/sign-up'
import { start } from './methods/auth/start' import { start } from './methods/auth/start'
import { getChatPreview } from './methods/chats/get-chat-preview'
import { getChat } from './methods/chats/get-chat'
import { getFullChat } from './methods/chats/get-full-chat'
import { joinChat } from './methods/chats/join-chat'
import { downloadAsBuffer } from './methods/files/download-buffer' import { downloadAsBuffer } from './methods/files/download-buffer'
import { downloadToFile } from './methods/files/download-file' import { downloadToFile } from './methods/files/download-file'
import { downloadAsIterable } from './methods/files/download-iterable' import { downloadAsIterable } from './methods/files/download-iterable'
@ -55,6 +59,7 @@ import { IMessageEntityParser } from './parser'
import { Readable } from 'stream' import { Readable } from 'stream'
import { import {
Chat, Chat,
ChatPreview,
FileDownloadParameters, FileDownloadParameters,
InputFileLike, InputFileLike,
InputMediaLike, InputMediaLike,
@ -320,6 +325,50 @@ export class TelegramClient extends BaseTelegramClient {
}): Promise<User> { }): Promise<User> {
return start.apply(this, arguments) return start.apply(this, arguments)
} }
/**
* Get preview information about a private chat.
*
* @param inviteLink Invite link
* @throws MtCuteArgumentError In case invite link has invalid format
* @throws MtCuteNotFoundError
* In case you are trying to get info about private chat that you have already joined.
* Use {@link getChat} or {@link getFullChat} instead.
*/
getChatPreview(inviteLink: string): Promise<ChatPreview> {
return getChatPreview.apply(this, arguments)
}
/**
* Get basic information about a chat.
*
* @param chatId ID of the chat, its username or invite link
* @throws MtCuteArgumentError
* In case you are trying to get info about private chat that you haven't joined.
* Use {@link getChatPreview} instead.
*/
getChat(chatId: InputPeerLike): Promise<Chat> {
return getChat.apply(this, arguments)
}
/**
* Get full information about a chat.
*
* @param chatId ID of the chat, its username or invite link
* @throws MtCuteArgumentError
* In case you are trying to get info about private chat that you haven't joined.
* Use {@link getChatPreview} instead.
*/
getFullChat(chatId: InputPeerLike): Promise<Chat> {
return getFullChat.apply(this, arguments)
}
/**
* Join a channel or supergroup
*
* @param chatId
* Chat identifier. Either an invite link (`t.me/joinchat/*`), a username (`@username`)
* or ID of the linked supergroup or channel.
*/
joinChat(chatId: InputPeerLike): Promise<Chat> {
return joinChat.apply(this, arguments)
}
/** /**
* Download a file and return its contents as a Buffer. * Download a file and return its contents as a Buffer.
* *

View file

@ -10,6 +10,7 @@ import { Readable } from 'stream'
import { import {
User, User,
Chat, Chat,
ChatPreview,
TermsOfService, TermsOfService,
SentCode, SentCode,
MaybeDynamic, MaybeDynamic,

View file

@ -0,0 +1,35 @@
import { MtCuteArgumentError, MtCuteNotFoundError } from '../../types'
import { TelegramClient } from '../../client'
import { INVITE_LINK_REGEX } from '../../utils/peer-utils'
import { ChatPreview } from '../../types'
/**
* Get preview information about a private chat.
*
* @param inviteLink Invite link
* @throws MtCuteArgumentError In case invite link has invalid format
* @throws MtCuteNotFoundError
* In case you are trying to get info about private chat that you have already joined.
* Use {@link getChat} or {@link getFullChat} instead.
* @internal
*/
export async function getChatPreview(
this: TelegramClient,
inviteLink: string
): Promise<ChatPreview> {
const m = inviteLink.match(INVITE_LINK_REGEX)
if (!m) throw new MtCuteArgumentError('Invalid invite link')
const res = await this.call({
_: 'messages.checkChatInvite',
hash: m[1],
})
if (res._ !== 'chatInvite') {
throw new MtCuteNotFoundError(
`You have already joined this chat!`
)
}
return new ChatPreview(this, res, inviteLink)
}

View file

@ -0,0 +1,65 @@
import { Chat, InputPeerLike, MtCuteArgumentError } from '../../types'
import { TelegramClient } from '../../client'
import {
INVITE_LINK_REGEX,
normalizeToInputChannel,
normalizeToInputPeer,
normalizeToInputUser,
} from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
/**
* Get basic information about a chat.
*
* @param chatId ID of the chat, its username or invite link
* @throws MtCuteArgumentError
* In case you are trying to get info about private chat that you haven't joined.
* Use {@link getChatPreview} instead.
* @internal
*/
export async function getChat(
this: TelegramClient,
chatId: InputPeerLike
): Promise<Chat> {
if (typeof chatId === 'string') {
const m = chatId.match(INVITE_LINK_REGEX)
if (m) {
const res = await this.call({
_: 'messages.checkChatInvite',
hash: m[1]
})
if (res._ === 'chatInvite') {
throw new MtCuteArgumentError(`You haven't joined ${JSON.stringify(res.title)}`)
}
return new Chat(this, res.chat)
}
}
const peer = await this.resolvePeer(chatId)
const input = normalizeToInputPeer(peer)
let res: tl.TypeChat | tl.TypeUser
if (input._ === 'inputPeerChannel') {
const r = await this.call({
_: 'channels.getChannels',
id: [normalizeToInputChannel(peer)!]
})
res = r.chats[0]
} else if (input._ === 'inputPeerUser' || input._ === 'inputPeerSelf') {
const r = await this.call({
_: 'users.getUsers',
id: [normalizeToInputUser(peer)!]
})
res = r[0]
} else if (input._ === 'inputPeerChat') {
const r = await this.call({
_: 'messages.getChats',
id: [input.chatId]
})
res = r.chats[0]
} else throw new Error('should not happen')
return new Chat(this, res)
}

View file

@ -0,0 +1,63 @@
import { Chat, InputPeerLike, MtCuteArgumentError } from '../../types'
import { TelegramClient } from '../../client'
import {
INVITE_LINK_REGEX,
normalizeToInputChannel,
normalizeToInputPeer,
normalizeToInputUser,
} from '../../utils/peer-utils'
import { tl } from '@mtcute/tl'
/**
* Get full information about a chat.
*
* @param chatId ID of the chat, its username or invite link
* @throws MtCuteArgumentError
* In case you are trying to get info about private chat that you haven't joined.
* Use {@link getChatPreview} instead.
* @internal
*/
export async function getFullChat(
this: TelegramClient,
chatId: InputPeerLike
): Promise<Chat> {
if (typeof chatId === 'string') {
const m = chatId.match(INVITE_LINK_REGEX)
if (m) {
const res = await this.call({
_: 'messages.checkChatInvite',
hash: m[1]
})
if (res._ === 'chatInvite') {
throw new MtCuteArgumentError(`You haven't joined ${JSON.stringify(res.title)}`)
}
// we still need to fetch full chat info
chatId = res.chat.id
}
}
const peer = await this.resolvePeer(chatId)
const input = normalizeToInputPeer(peer)
let res: tl.messages.TypeChatFull | tl.TypeUserFull
if (input._ === 'inputPeerChannel') {
res = await this.call({
_: 'channels.getFullChannel',
channel: normalizeToInputChannel(peer)!
})
} else if (input._ === 'inputPeerUser' || input._ === 'inputPeerSelf') {
res = await this.call({
_: 'users.getFullUser',
id: normalizeToInputUser(peer)!
})
} else if (input._ === 'inputPeerChat') {
res = await this.call({
_: 'messages.getFullChat',
chatId: input.chatId
})
} else throw new Error('should not happen')
return Chat._parseFull(this, res)
}

View file

@ -0,0 +1,57 @@
import { TelegramClient } from '../../client'
import {
Chat,
InputPeerLike,
MtCuteNotFoundError,
MtCuteTypeAssertionError,
} from '../../types'
import { INVITE_LINK_REGEX, normalizeToInputChannel } from '../../utils/peer-utils'
/**
* Join a channel or supergroup
*
* @param chatId
* Chat identifier. Either an invite link (`t.me/joinchat/*`), a username (`@username`)
* or ID of the linked supergroup or channel.
* @internal
*/
export async function joinChat(
this: TelegramClient,
chatId: InputPeerLike
): Promise<Chat> {
if (typeof chatId === 'string') {
const m = chatId.match(INVITE_LINK_REGEX)
if (m) {
const res = await this.call({
_: 'messages.importChatInvite',
hash: m[1],
})
if (!(res._ === 'updates' || res._ === 'updatesCombined')) {
throw new MtCuteTypeAssertionError(
'joinChat, (@ messages.importChatInvite)',
'updates | updatesCombined',
res._
)
}
return new Chat(this, res.chats[0])
}
}
const peer = normalizeToInputChannel(await this.resolvePeer(chatId))
if (!peer) throw new MtCuteNotFoundError()
const res = await this.call({
_: 'channels.joinChannel',
channel: peer,
})
if (!(res._ === 'updates' || res._ === 'updatesCombined')) {
throw new MtCuteTypeAssertionError(
'joinChat, (@ channels.joinChannel)',
'updates | updatesCombined',
res._
)
}
return new Chat(this, res.chats[0])
}

View file

@ -0,0 +1,101 @@
import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client'
import { makeInspectable } from '../utils'
import { Photo } from '../media'
import { User } from './user'
import { Chat } from './chat'
export namespace ChatPreview {
/**
* Chat type. Can be:
* - `group`: Legacy group
* - `supergroup`: Supergroup
* - `channel`: Broadcast channel
* - `broadcast`: Broadcast group
*/
export type Type = 'group' | 'supergroup' | 'channel' | 'broadcast'
}
export class ChatPreview {
readonly client: TelegramClient
readonly invite: tl.RawChatInvite
/**
* Original invite link used to fetch
* this preview
*/
readonly link: string
constructor(client: TelegramClient, raw: tl.RawChatInvite, link: string) {
this.client = client
this.invite = raw
this.link = link
}
/**
* Title of the chat
*/
get title(): string {
return this.invite.title
}
/**
* Type of the chat
*/
get type(): ChatPreview.Type {
if (!this.invite.channel) return 'group'
if (this.invite.broadcast) return 'channel'
if (this.invite.megagroup) return 'broadcast'
return 'supergroup'
}
/**
* Total chat member count
*/
get memberCount(): number {
return this.invite.participantsCount
}
_photo?: Photo
/**
* Chat photo
*/
get photo(): Photo | null {
if (this.invite.photo._ === 'photoEmpty') return null
if (!this._photo) {
this._photo = new Photo(this.client, this.invite.photo)
}
return this._photo
}
private _someMembers?: User[]
/**
* Preview of some of the chat members.
*
* This usually contains around 10 members,
* and members that are inside your contacts list are
* ordered before others.
*/
get someMembers(): User[] {
if (!this._someMembers) {
this._someMembers = this.invite.participants
? this.invite.participants.map(
(it) => new User(this.client, it as tl.RawUser)
)
: []
}
return this._someMembers
}
/**
* Join this chat
*/
async join(): Promise<Chat> {
return this.client.joinChat(this.link)
}
}
makeInspectable(ChatPreview, ['link'])

View file

@ -14,8 +14,15 @@ export namespace Chat {
* - `group`: Legacy group * - `group`: Legacy group
* - `supergroup`: Supergroup * - `supergroup`: Supergroup
* - `channel`: Broadcast channel * - `channel`: Broadcast channel
* - `broadcast`: Broadcast group
*/ */
export type Type = 'private' | 'bot' | 'group' | 'supergroup' | 'channel' export type Type =
| 'private'
| 'bot'
| 'group'
| 'supergroup'
| 'channel'
| 'broadcast'
} }
/** /**
@ -109,7 +116,11 @@ export class Chat {
} else if (this.peer._ === 'chat') { } else if (this.peer._ === 'chat') {
this._type = 'group' this._type = 'group'
} else if (this.peer._ === 'channel') { } else if (this.peer._ === 'channel') {
this._type = this.peer.broadcast ? 'channel' : 'supergroup' this._type = this.peer.megagroup
? 'broadcast'
: this.peer.broadcast
? 'channel'
: 'supergroup'
} }
} }
@ -379,6 +390,7 @@ export class Chat {
return new Chat(client, chats[peer.channelId]) return new Chat(client, chats[peer.channelId])
} }
/** @internal */
static _parseFull( static _parseFull(
client: TelegramClient, client: TelegramClient,
full: tl.messages.RawChatFull | tl.RawUserFull full: tl.messages.RawChatFull | tl.RawUserFull
@ -409,6 +421,13 @@ export class Chat {
} }
// todo: bound methods https://github.com/pyrogram/pyrogram/blob/a86656aefcc93cc3d2f5c98227d5da28fcddb136/pyrogram/types/user_and_chats/chat.py#L319 // todo: bound methods https://github.com/pyrogram/pyrogram/blob/a86656aefcc93cc3d2f5c98227d5da28fcddb136/pyrogram/types/user_and_chats/chat.py#L319
/**
* Join this chat.
*/
async join(): Promise<void> {
await this.client.joinChat(this.inputPeer)
}
} }
makeInspectable(Chat) makeInspectable(Chat)

View file

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

View file

@ -1,5 +1,8 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
export const INVITE_LINK_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)\/joinchat\/)([\w-]+)$/i
// helpers to normalize result of `resolvePeer` function // helpers to normalize result of `resolvePeer` function
export function normalizeToInputPeer( export function normalizeToInputPeer(