feat: forums
closes MTQ-77
This commit is contained in:
parent
b2fccf4978
commit
00f30a6495
27 changed files with 1133 additions and 71 deletions
|
@ -100,6 +100,17 @@ import { _normalizeInputFile } from './methods/files/normalize-input-file'
|
|||
import { _normalizeInputMedia } from './methods/files/normalize-input-media'
|
||||
import { uploadFile } from './methods/files/upload-file'
|
||||
import { uploadMedia } from './methods/files/upload-media'
|
||||
import { createForumTopic } from './methods/forums/create-forum-topic'
|
||||
import { deleteForumTopicHistory } from './methods/forums/delete-forum-topic-history'
|
||||
import { editForumTopic } from './methods/forums/edit-forum-topic'
|
||||
import { getForumTopics, GetForumTopicsOffset } from './methods/forums/get-forum-topics'
|
||||
import { getForumTopicsById } from './methods/forums/get-forum-topics-by-id'
|
||||
import { iterForumTopics } from './methods/forums/iter-forum-topics'
|
||||
import { reorderPinnedForumTopics } from './methods/forums/reorder-pinned-forum-topics'
|
||||
import { toggleForum } from './methods/forums/toggle-forum'
|
||||
import { toggleForumTopicClosed } from './methods/forums/toggle-forum-topic-closed'
|
||||
import { toggleForumTopicPinned } from './methods/forums/toggle-forum-topic-pinned'
|
||||
import { toggleGeneralTopicHidden } from './methods/forums/toggle-general-topic-hidden'
|
||||
import { createInviteLink } from './methods/invite-links/create-invite-link'
|
||||
import { editInviteLink } from './methods/invite-links/edit-invite-link'
|
||||
import { exportInviteLink } from './methods/invite-links/export-invite-link'
|
||||
|
@ -221,6 +232,7 @@ import {
|
|||
Dialog,
|
||||
FileDownloadParameters,
|
||||
FormattedString,
|
||||
ForumTopic,
|
||||
GameHighScore,
|
||||
HistoryReadUpdate,
|
||||
IMessageEntityParser,
|
||||
|
@ -1045,11 +1057,19 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
/**
|
||||
* Create a new broadcast channel
|
||||
*
|
||||
* @param title Channel title
|
||||
* @param description (default: `''`) Channel description
|
||||
* @returns Newly created channel
|
||||
*/
|
||||
createChannel(title: string, description?: string): Promise<Chat>
|
||||
createChannel(params: {
|
||||
/**
|
||||
* Channel title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Channel description
|
||||
*/
|
||||
description?: string
|
||||
}): Promise<Chat>
|
||||
/**
|
||||
* Create a legacy group chat
|
||||
*
|
||||
|
@ -1065,10 +1085,31 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
/**
|
||||
* Create a new supergroup
|
||||
*
|
||||
* @param title Title of the supergroup
|
||||
* @param description (default: `''`) Description of the supergroup
|
||||
* @returns Newly created supergroup
|
||||
*/
|
||||
createSupergroup(title: string, description?: string): Promise<Chat>
|
||||
createSupergroup(params: {
|
||||
/**
|
||||
* Supergroup title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Supergroup description
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Whether to create a forum
|
||||
*/
|
||||
forum?: boolean
|
||||
|
||||
/**
|
||||
* TTL period (in seconds) for the newly created channel
|
||||
*
|
||||
* @default 0 (i.e. messages don't expire)
|
||||
*/
|
||||
ttlPeriod?: number
|
||||
}): Promise<Chat>
|
||||
|
||||
/**
|
||||
* Delete a channel or a supergroup
|
||||
|
@ -1916,6 +1957,193 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
): Promise<Extract<MessageMedia, Photo | RawDocument>>
|
||||
/**
|
||||
* Create a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @returns Service message for the created topic
|
||||
*/
|
||||
createForumTopic(
|
||||
chatId: InputPeerLike,
|
||||
params: {
|
||||
/**
|
||||
* Topic title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Icon of the topic.
|
||||
*
|
||||
* Can be a number (color in RGB, see {@link ForumTopic} static members for allowed values)
|
||||
* or a custom emoji ID.
|
||||
*
|
||||
* Icon color can't be changed after the topic is created.
|
||||
*/
|
||||
icon?: number | tl.Long
|
||||
|
||||
/**
|
||||
* Send as a specific channel
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
},
|
||||
): Promise<Message>
|
||||
/**
|
||||
* Delete a forum topic and all its history
|
||||
*
|
||||
* @param chat Chat or user ID, username, phone number, `"me"` or `"self"`
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
deleteForumTopicHistory(chat: InputPeerLike, topicId: number): Promise<void>
|
||||
/**
|
||||
* Modify a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @returns Service message about the modification
|
||||
*/
|
||||
editForumTopic(
|
||||
chatId: InputPeerLike,
|
||||
topicId: number,
|
||||
params: {
|
||||
/**
|
||||
* New topic title
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* New icon of the topic.
|
||||
*
|
||||
* Can be a custom emoji ID, or `null` to remove the icon
|
||||
* and use static color instead
|
||||
*/
|
||||
icon?: tl.Long | null
|
||||
},
|
||||
): Promise<Message>
|
||||
/**
|
||||
* Get a single forum topic by its ID
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
*/
|
||||
getForumTopicsById(chatId: InputPeerLike, ids: number): Promise<ForumTopic>
|
||||
/**
|
||||
* Get forum topics by their IDs
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
*/
|
||||
getForumTopicsById(chatId: InputPeerLike, ids: number[]): Promise<ForumTopic[]>
|
||||
/**
|
||||
* Get forum topics
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
*/
|
||||
getForumTopics(
|
||||
chatId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Search query
|
||||
*/
|
||||
query?: string
|
||||
|
||||
/**
|
||||
* Offset for pagination
|
||||
*/
|
||||
offset?: GetForumTopicsOffset
|
||||
|
||||
/**
|
||||
* Maximum number of topics to return.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<ForumTopic, GetForumTopicsOffset>>
|
||||
/**
|
||||
* Iterate over forum topics. Wrapper over {@link getForumTopics}.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
*/
|
||||
iterForumTopics(
|
||||
chatId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getForumTopics']>[1] & {
|
||||
/**
|
||||
* Maximum number of topics to return.
|
||||
*
|
||||
* @default `Infinity`, i.e. return all topics
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Chunk size. Usually you shouldn't care about this.
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<ForumTopic>
|
||||
/**
|
||||
* Reorder pinned forum topics
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
reorderPinnedForumTopics(
|
||||
chatId: InputPeerLike,
|
||||
params: {
|
||||
/**
|
||||
* Order of the pinned topics
|
||||
*/
|
||||
order: number[]
|
||||
|
||||
/**
|
||||
* Whether to un-pin topics not present in the order
|
||||
*/
|
||||
force?: boolean
|
||||
},
|
||||
): Promise<void>
|
||||
/**
|
||||
* Toggle open/close status of a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @param closed Whether the topic should be closed
|
||||
* @returns Service message about the modification
|
||||
*/
|
||||
toggleForumTopicClosed(chatId: InputPeerLike, topicId: number, closed: boolean): Promise<Message>
|
||||
/**
|
||||
* Toggle whether a topic in a forum is pinned
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @param pinned Whether the topic should be pinned
|
||||
*/
|
||||
toggleForumTopicPinned(chatId: InputPeerLike, topicId: number, pinned: boolean): Promise<void>
|
||||
/**
|
||||
* Set whether a supergroup is a forum.
|
||||
*
|
||||
* Only owner of the supergroup can change this setting.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param enabled (default: `false`) Whether the supergroup should be a forum
|
||||
*/
|
||||
toggleForum(chatId: InputPeerLike, enabled?: boolean): Promise<void>
|
||||
/**
|
||||
* Toggle whether "General" topic in a forum is hidden or not
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param hidden Whether the topic should be hidden
|
||||
* @returns Service message about the modification
|
||||
*/
|
||||
toggleGeneralTopicHidden(chatId: InputPeerLike, hidden: boolean): Promise<Message>
|
||||
/**
|
||||
* Create an additional invite link for the chat.
|
||||
*
|
||||
|
@ -3061,6 +3289,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
@ -3116,6 +3346,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
@ -3226,6 +3458,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
@ -3353,6 +3587,8 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
@ -3509,7 +3745,15 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*
|
||||
* @param chatId Chat or user ID
|
||||
*/
|
||||
unpinAllMessages(chatId: InputPeerLike): Promise<void>
|
||||
unpinAllMessages(
|
||||
chatId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* For forums - unpin only messages from the given topic
|
||||
*/
|
||||
topicId?: number
|
||||
},
|
||||
): Promise<void>
|
||||
/**
|
||||
* Unpin a message in a group, supergroup, channel or PM.
|
||||
*
|
||||
|
@ -4195,6 +4439,17 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
_normalizeInputMedia = _normalizeInputMedia
|
||||
uploadFile = uploadFile
|
||||
uploadMedia = uploadMedia
|
||||
createForumTopic = createForumTopic
|
||||
deleteForumTopicHistory = deleteForumTopicHistory
|
||||
editForumTopic = editForumTopic
|
||||
getForumTopicsById = getForumTopicsById
|
||||
getForumTopics = getForumTopics
|
||||
iterForumTopics = iterForumTopics
|
||||
reorderPinnedForumTopics = reorderPinnedForumTopics
|
||||
toggleForumTopicClosed = toggleForumTopicClosed
|
||||
toggleForumTopicPinned = toggleForumTopicPinned
|
||||
toggleForum = toggleForum
|
||||
toggleGeneralTopicHidden = toggleGeneralTopicHidden
|
||||
createInviteLink = createInviteLink
|
||||
editInviteLink = editInviteLink
|
||||
exportInviteLink = exportInviteLink
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
Dialog,
|
||||
FileDownloadParameters,
|
||||
FormattedString,
|
||||
ForumTopic,
|
||||
GameHighScore,
|
||||
HistoryReadUpdate,
|
||||
IMessageEntityParser,
|
||||
|
|
|
@ -5,12 +5,25 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
|||
/**
|
||||
* Create a new broadcast channel
|
||||
*
|
||||
* @param title Channel title
|
||||
* @param description Channel description
|
||||
* @returns Newly created channel
|
||||
* @internal
|
||||
*/
|
||||
export async function createChannel(this: TelegramClient, title: string, description = ''): Promise<Chat> {
|
||||
export async function createChannel(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Channel title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Channel description
|
||||
*/
|
||||
description?: string
|
||||
},
|
||||
): Promise<Chat> {
|
||||
const { title, description = '' } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.createChannel',
|
||||
title,
|
||||
|
|
|
@ -5,16 +5,44 @@ import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
|||
/**
|
||||
* Create a new supergroup
|
||||
*
|
||||
* @param title Title of the supergroup
|
||||
* @param description Description of the supergroup
|
||||
* @returns Newly created supergroup
|
||||
* @internal
|
||||
*/
|
||||
export async function createSupergroup(this: TelegramClient, title: string, description = ''): Promise<Chat> {
|
||||
export async function createSupergroup(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Supergroup title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Supergroup description
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Whether to create a forum
|
||||
*/
|
||||
forum?: boolean
|
||||
|
||||
/**
|
||||
* TTL period (in seconds) for the newly created channel
|
||||
*
|
||||
* @default 0 (i.e. messages don't expire)
|
||||
*/
|
||||
ttlPeriod?: number
|
||||
},
|
||||
): Promise<Chat> {
|
||||
const { title, description = '', forum, ttlPeriod = 0 } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.createChannel',
|
||||
title,
|
||||
about: description,
|
||||
megagroup: true,
|
||||
forum,
|
||||
ttlPeriod,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('channels.createChannel', res)
|
||||
|
|
55
packages/client/src/methods/forums/create-forum-topic.ts
Normal file
55
packages/client/src/methods/forums/create-forum-topic.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
import { randomLong } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Message } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Create a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @returns Service message for the created topic
|
||||
* @internal
|
||||
*/
|
||||
export async function createForumTopic(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params: {
|
||||
/**
|
||||
* Topic title
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Icon of the topic.
|
||||
*
|
||||
* Can be a number (color in RGB, see {@link ForumTopic} static members for allowed values)
|
||||
* or a custom emoji ID.
|
||||
*
|
||||
* Icon color can't be changed after the topic is created.
|
||||
*/
|
||||
icon?: number | tl.Long
|
||||
|
||||
/**
|
||||
* Send as a specific channel
|
||||
*/
|
||||
sendAs?: InputPeerLike
|
||||
},
|
||||
): Promise<Message> {
|
||||
const { title, icon, sendAs } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.createForumTopic',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
title,
|
||||
iconColor: typeof icon === 'number' ? icon : undefined,
|
||||
iconEmojiId: typeof icon !== 'number' ? icon : undefined,
|
||||
sendAs: sendAs ? await this.resolvePeer(sendAs) : undefined,
|
||||
randomId: randomLong(),
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { assertTypeIsNot } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
import { createDummyUpdate } from '../../utils/updates-utils'
|
||||
|
||||
/**
|
||||
* Delete a forum topic and all its history
|
||||
*
|
||||
* @param chat Chat or user ID, username, phone number, `"me"` or `"self"`
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @internal
|
||||
*/
|
||||
export async function deleteForumTopicHistory(
|
||||
this: TelegramClient,
|
||||
chat: InputPeerLike,
|
||||
topicId: number,
|
||||
): Promise<void> {
|
||||
const channel = normalizeToInputChannel(await this.resolvePeer(chat), chat)
|
||||
assertTypeIsNot('deleteForumTopicHistory', channel, 'inputChannelEmpty')
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.deleteTopicHistory',
|
||||
channel,
|
||||
topMsgId: topicId,
|
||||
})
|
||||
|
||||
this._handleUpdate(createDummyUpdate(res.pts, res.ptsCount, channel.channelId))
|
||||
}
|
49
packages/client/src/methods/forums/edit-forum-topic.ts
Normal file
49
packages/client/src/methods/forums/edit-forum-topic.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import Long from 'long'
|
||||
|
||||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Message } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Modify a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @returns Service message about the modification
|
||||
* @internal
|
||||
*/
|
||||
export async function editForumTopic(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
topicId: number,
|
||||
params: {
|
||||
/**
|
||||
* New topic title
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* New icon of the topic.
|
||||
*
|
||||
* Can be a custom emoji ID, or `null` to remove the icon
|
||||
* and use static color instead
|
||||
*/
|
||||
icon?: tl.Long | null
|
||||
},
|
||||
): Promise<Message> {
|
||||
const { title, icon } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.editForumTopic',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
topicId,
|
||||
title,
|
||||
iconEmojiId: icon ? icon ?? Long.ZERO : undefined,
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
50
packages/client/src/methods/forums/get-forum-topics-by-id.ts
Normal file
50
packages/client/src/methods/forums/get-forum-topics-by-id.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { ForumTopic, InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils'
|
||||
|
||||
/**
|
||||
* Get a single forum topic by its ID
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function getForumTopicsById(this: TelegramClient, chatId: InputPeerLike, ids: number): Promise<ForumTopic>
|
||||
|
||||
/**
|
||||
* Get forum topics by their IDs
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function getForumTopicsById(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
ids: number[],
|
||||
): Promise<ForumTopic[]>
|
||||
|
||||
/**
|
||||
* Get forum topics by their IDs
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function getForumTopicsById(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
ids: MaybeArray<number>,
|
||||
): Promise<MaybeArray<ForumTopic>> {
|
||||
const single = !Array.isArray(ids)
|
||||
if (single) ids = [ids as number]
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.getForumTopicsByID',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId)),
|
||||
topics: ids as number[],
|
||||
})
|
||||
|
||||
const topics = ForumTopic.parseTlForumTopics(this, res)
|
||||
|
||||
return single ? topics[0] : topics
|
||||
}
|
77
packages/client/src/methods/forums/get-forum-topics.ts
Normal file
77
packages/client/src/methods/forums/get-forum-topics.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { ArrayPaginated, ForumTopic, InputPeerLike } from '../../types'
|
||||
import { makeArrayPaginated } from '../../utils'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
// @exported
|
||||
export interface GetForumTopicsOffset {
|
||||
date: number
|
||||
id: number
|
||||
topic: number
|
||||
}
|
||||
|
||||
const defaultOffset: GetForumTopicsOffset = {
|
||||
date: 0,
|
||||
id: 0,
|
||||
topic: 0,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forum topics
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function getForumTopics(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Search query
|
||||
*/
|
||||
query?: string
|
||||
|
||||
/**
|
||||
* Offset for pagination
|
||||
*/
|
||||
offset?: GetForumTopicsOffset
|
||||
|
||||
/**
|
||||
* Maximum number of topics to return.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<ForumTopic, GetForumTopicsOffset>> {
|
||||
if (!params) params = {}
|
||||
|
||||
const {
|
||||
query,
|
||||
offset: { date: offsetDate, id: offsetId, topic: offsetTopic } = defaultOffset,
|
||||
limit = 100,
|
||||
} = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'channels.getForumTopics',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
q: query,
|
||||
offsetDate,
|
||||
offsetId,
|
||||
offsetTopic,
|
||||
limit,
|
||||
})
|
||||
|
||||
const topics = ForumTopic.parseTlForumTopics(this, res)
|
||||
|
||||
const last = topics[topics.length - 1]
|
||||
const next = last ?
|
||||
{
|
||||
date: res.orderByCreateDate ? last.raw.date : last.lastMessage.raw.date,
|
||||
id: last.raw.topMessage,
|
||||
topic: last.raw.id,
|
||||
} :
|
||||
undefined
|
||||
|
||||
return makeArrayPaginated(topics, res.count, next)
|
||||
}
|
53
packages/client/src/methods/forums/iter-forum-topics.ts
Normal file
53
packages/client/src/methods/forums/iter-forum-topics.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { ForumTopic, InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Iterate over forum topics. Wrapper over {@link getForumTopics}.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @internal
|
||||
*/
|
||||
export async function* iterForumTopics(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getForumTopics']>[1] & {
|
||||
/**
|
||||
* Maximum number of topics to return.
|
||||
*
|
||||
* @default `Infinity`, i.e. return all topics
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Chunk size. Usually you shouldn't care about this.
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<ForumTopic> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { query, limit = Infinity, chunkSize = 100 } = params
|
||||
|
||||
const peer = normalizeToInputChannel(await this.resolvePeer(chatId))
|
||||
|
||||
let { offset } = params
|
||||
let current = 0
|
||||
|
||||
for (;;) {
|
||||
const res = await this.getForumTopics(peer, {
|
||||
query,
|
||||
offset,
|
||||
limit: Math.min(chunkSize, limit - current),
|
||||
})
|
||||
|
||||
for (const topic of res) {
|
||||
yield topic
|
||||
|
||||
if (++current >= limit) return
|
||||
}
|
||||
|
||||
if (!res.next) return
|
||||
offset = res.next
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Reorder pinned forum topics
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @internal
|
||||
*/
|
||||
export async function reorderPinnedForumTopics(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params: {
|
||||
/**
|
||||
* Order of the pinned topics
|
||||
*/
|
||||
order: number[]
|
||||
|
||||
/**
|
||||
* Whether to un-pin topics not present in the order
|
||||
*/
|
||||
force?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
const { order, force } = params
|
||||
await this.call({
|
||||
_: 'channels.reorderPinnedForumTopics',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
order,
|
||||
force,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Message } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Toggle open/close status of a topic in a forum
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @param closed Whether the topic should be closed
|
||||
* @returns Service message about the modification
|
||||
* @internal
|
||||
*/
|
||||
export async function toggleForumTopicClosed(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
topicId: number,
|
||||
closed: boolean,
|
||||
): Promise<Message> {
|
||||
const res = await this.call({
|
||||
_: 'channels.editForumTopic',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
topicId,
|
||||
closed,
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Toggle whether a topic in a forum is pinned
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param topicId ID of the topic (i.e. its top message ID)
|
||||
* @param pinned Whether the topic should be pinned
|
||||
* @internal
|
||||
*/
|
||||
export async function toggleForumTopicPinned(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
topicId: number,
|
||||
pinned: boolean,
|
||||
): Promise<void> {
|
||||
await this.call({
|
||||
_: 'channels.updatePinnedForumTopic',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
topicId,
|
||||
pinned,
|
||||
})
|
||||
}
|
21
packages/client/src/methods/forums/toggle-forum.ts
Normal file
21
packages/client/src/methods/forums/toggle-forum.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Set whether a supergroup is a forum.
|
||||
*
|
||||
* Only owner of the supergroup can change this setting.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param enabled Whether the supergroup should be a forum
|
||||
* @internal
|
||||
*/
|
||||
export async function toggleForum(this: TelegramClient, chatId: InputPeerLike, enabled = false): Promise<void> {
|
||||
const res = await this.call({
|
||||
_: 'channels.toggleForum',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
enabled,
|
||||
})
|
||||
this._handleUpdate(res)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Message } from '../../types'
|
||||
import { normalizeToInputChannel } from '../../utils/peer-utils'
|
||||
|
||||
/**
|
||||
* Toggle whether "General" topic in a forum is hidden or not
|
||||
*
|
||||
* Only admins with `manageTopics` permission can do this.
|
||||
*
|
||||
* @param chatId Chat ID or username
|
||||
* @param hidden Whether the topic should be hidden
|
||||
* @returns Service message about the modification
|
||||
* @internal
|
||||
*/
|
||||
export async function toggleGeneralTopicHidden(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
hidden: boolean,
|
||||
): Promise<Message> {
|
||||
const res = await this.call({
|
||||
_: 'channels.editForumTopic',
|
||||
channel: normalizeToInputChannel(await this.resolvePeer(chatId), chatId),
|
||||
topicId: 1,
|
||||
hidden,
|
||||
})
|
||||
|
||||
return this._findMessageInUpdate(res)
|
||||
}
|
|
@ -57,6 +57,8 @@ export async function sendCopy(
|
|||
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ export async function sendMediaGroup(
|
|||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ export async function sendMedia(
|
|||
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ export async function sendText(
|
|||
params?: {
|
||||
/**
|
||||
* Message to reply to. Either a message object or message ID.
|
||||
*
|
||||
* For forums - can also be an ID of the topic (i.e. its top message ID)
|
||||
*/
|
||||
replyTo?: number | Message
|
||||
|
||||
|
|
|
@ -9,12 +9,24 @@ import { createDummyUpdate } from '../../utils/updates-utils'
|
|||
* @param chatId Chat or user ID
|
||||
* @internal
|
||||
*/
|
||||
export async function unpinAllMessages(this: TelegramClient, chatId: InputPeerLike): Promise<void> {
|
||||
export async function unpinAllMessages(
|
||||
this: TelegramClient,
|
||||
chatId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* For forums - unpin only messages from the given topic
|
||||
*/
|
||||
topicId?: number
|
||||
},
|
||||
): Promise<void> {
|
||||
const { topicId } = params ?? {}
|
||||
|
||||
const peer = await this.resolvePeer(chatId)
|
||||
|
||||
const res = await this.call({
|
||||
_: 'messages.unpinAllMessages',
|
||||
peer,
|
||||
topMsgId: topicId,
|
||||
})
|
||||
|
||||
if (isInputPeerChannel(peer)) {
|
||||
|
|
|
@ -25,7 +25,7 @@ export class Dialog {
|
|||
readonly client: TelegramClient,
|
||||
readonly raw: tl.RawDialog,
|
||||
readonly _peers: PeersIndex,
|
||||
readonly _messages: Record<number, tl.TypeMessage>,
|
||||
readonly _messages: Map<number, tl.TypeMessage>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -44,11 +44,11 @@ export class Dialog {
|
|||
|
||||
const peers = PeersIndex.from(dialogs)
|
||||
|
||||
const messages: Record<number, tl.TypeMessage> = {}
|
||||
const messages = new Map<number, tl.TypeMessage>()
|
||||
dialogs.messages.forEach((msg) => {
|
||||
if (!msg.peerId) return
|
||||
|
||||
messages[getMarkedPeerId(msg.peerId)] = msg
|
||||
messages.set(getMarkedPeerId(msg.peerId), msg)
|
||||
})
|
||||
|
||||
const arr = dialogs.dialogs
|
||||
|
@ -228,8 +228,8 @@ export class Dialog {
|
|||
if (!this._lastMessage) {
|
||||
const cid = this.chat.id
|
||||
|
||||
if (cid in this._messages) {
|
||||
this._lastMessage = new Message(this.client, this._messages[cid], this._peers)
|
||||
if (this._messages.has(cid)) {
|
||||
this._lastMessage = new Message(this.client, this._messages.get(cid)!, this._peers)
|
||||
} else {
|
||||
throw new MtMessageNotFoundError(cid, 0)
|
||||
}
|
||||
|
@ -267,12 +267,19 @@ export class Dialog {
|
|||
}
|
||||
|
||||
/**
|
||||
* Number of unread messages
|
||||
* Number of unread mentions
|
||||
*/
|
||||
get unreadMentionsCount(): number {
|
||||
return this.raw.unreadMentionsCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of unread reactions
|
||||
*/
|
||||
get unreadReactionsCount(): number {
|
||||
return this.raw.unreadReactionsCount
|
||||
}
|
||||
|
||||
private _draftMessage?: DraftMessage | null
|
||||
/**
|
||||
* Draft message in this dialog
|
||||
|
@ -280,7 +287,7 @@ export class Dialog {
|
|||
get draftMessage(): DraftMessage | null {
|
||||
if (this._draftMessage === undefined) {
|
||||
if (this.raw.draft?._ === 'draftMessage') {
|
||||
this._draftMessage = new DraftMessage(this.client, this.raw.draft, this.chat.inputPeer)
|
||||
this._draftMessage = new DraftMessage(this.client, this.raw.draft)
|
||||
} else {
|
||||
this._draftMessage = null
|
||||
}
|
||||
|
|
|
@ -2,16 +2,13 @@ import { tl } from '@mtcute/core'
|
|||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { InputMediaLike } from '../media'
|
||||
import { InputPeerLike } from '../peers'
|
||||
import { Message } from './message'
|
||||
import { MessageEntity } from './message-entity'
|
||||
|
||||
/**
|
||||
* A draft message
|
||||
*/
|
||||
export class DraftMessage {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawDraftMessage, readonly _chatId: InputPeerLike) {}
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawDraftMessage) {}
|
||||
|
||||
/**
|
||||
* Text of the draft message
|
||||
|
@ -59,45 +56,6 @@ export class DraftMessage {
|
|||
|
||||
return this._entities
|
||||
}
|
||||
|
||||
/**
|
||||
* Send this draft as a message.
|
||||
* Calling this method will clear current draft.
|
||||
*
|
||||
* @param params Additional sending parameters
|
||||
* @link TelegramClient.sendText
|
||||
*/
|
||||
send(params?: Parameters<TelegramClient['sendText']>[2]): Promise<Message> {
|
||||
return this.client.sendText(this._chatId, this.raw.message, {
|
||||
clearDraft: true,
|
||||
disableWebPreview: this.raw.noWebpage,
|
||||
entities: this.raw.entities,
|
||||
replyTo: this.raw.replyToMsgId,
|
||||
...(params || {}),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send this draft as a message with media.
|
||||
* Calling this method will clear current draft.
|
||||
*
|
||||
* If passed media does not have an
|
||||
* explicit caption, it will be set to {@link text},
|
||||
* and its entities to {@link entities}
|
||||
*
|
||||
* @param media Media to be sent
|
||||
* @param params Additional sending parameters
|
||||
* @link TelegramClient.sendMedia
|
||||
*/
|
||||
sendWithMedia(media: InputMediaLike, params?: Parameters<TelegramClient['sendMedia']>[2]): Promise<Message> {
|
||||
return this.client.sendMedia(this._chatId, media, {
|
||||
clearDraft: true,
|
||||
replyTo: this.raw.replyToMsgId,
|
||||
caption: this.raw.message,
|
||||
entities: this.raw.entities,
|
||||
...(params || {}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(DraftMessage)
|
||||
|
|
|
@ -249,6 +249,37 @@ export interface ActionSetTtl {
|
|||
readonly period: number
|
||||
}
|
||||
|
||||
/** Forum topic was created */
|
||||
export interface ActionTopicCreated {
|
||||
readonly type: 'topic_created'
|
||||
|
||||
/** Title of the topic */
|
||||
title: string
|
||||
|
||||
/** Icon color of the topic */
|
||||
iconColor: number
|
||||
|
||||
/** Icon emoji of the topic */
|
||||
iconCustomEmoji?: tl.Long
|
||||
}
|
||||
|
||||
/** Forum topic was modified */
|
||||
export interface ActionTopicEdited {
|
||||
readonly type: 'topic_edited'
|
||||
|
||||
/** New title of the topic */
|
||||
title?: string
|
||||
|
||||
/** New icon emoji of the topic (may be empty) */
|
||||
iconCustomEmoji?: tl.Long
|
||||
|
||||
/** Whether the topic was opened/closed */
|
||||
closed?: boolean
|
||||
|
||||
/** Whether the topic was (un-)hidden - only for "General" topic (`id=1`) */
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type MessageAction =
|
||||
| ActionChatCreated
|
||||
| ActionChannelCreated
|
||||
|
@ -275,6 +306,8 @@ export type MessageAction =
|
|||
| ActionGroupCallEnded
|
||||
| ActionGroupInvite
|
||||
| ActionSetTtl
|
||||
| ActionTopicCreated
|
||||
| ActionTopicEdited
|
||||
| null
|
||||
|
||||
/** @internal */
|
||||
|
@ -426,6 +459,21 @@ export function _messageActionFromTl(this: Message, act: tl.TypeMessageAction):
|
|||
type: 'set_ttl',
|
||||
period: act.period,
|
||||
}
|
||||
case 'messageActionTopicCreate':
|
||||
return {
|
||||
type: 'topic_created',
|
||||
title: act.title,
|
||||
iconColor: act.iconColor,
|
||||
iconCustomEmoji: act.iconEmojiId,
|
||||
}
|
||||
case 'messageActionTopicEdit':
|
||||
return {
|
||||
type: 'topic_edited',
|
||||
title: act.title,
|
||||
iconCustomEmoji: act.iconEmojiId,
|
||||
closed: act.closed,
|
||||
hidden: act.hidden,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { PeersIndex, TelegramClient, toggleChannelIdMark } from '../../..'
|
||||
import { ForumTopic, PeersIndex, TelegramClient, toggleChannelIdMark } from '../../..'
|
||||
import { Photo } from '../../media'
|
||||
import { Message } from '../../messages'
|
||||
import { ChatInviteLink } from '../chat-invite-link'
|
||||
|
@ -297,6 +298,41 @@ export interface ChatActionTtlChanged {
|
|||
new: number
|
||||
}
|
||||
|
||||
/** Forum has been toggled */
|
||||
export interface ChatActionForumToggled {
|
||||
type: 'forum_toggled'
|
||||
|
||||
/** New status */
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
/** Forum topic has been created */
|
||||
export interface ChatActionTopicCreated {
|
||||
type: 'topic_created'
|
||||
|
||||
/** Topic that has been created */
|
||||
topic: ForumTopic
|
||||
}
|
||||
|
||||
/** Forum topic has been edited */
|
||||
export interface ChatActionTopicEdited {
|
||||
type: 'topic_edited'
|
||||
|
||||
/** Old topic info */
|
||||
old: ForumTopic
|
||||
|
||||
/** New topic info */
|
||||
new: ForumTopic
|
||||
}
|
||||
|
||||
/** Forum topic has been edited */
|
||||
export interface ChatActionTopicDeleted {
|
||||
type: 'topic_deleted'
|
||||
|
||||
/** Old topic info */
|
||||
topic: ForumTopic
|
||||
}
|
||||
|
||||
/** Chat event action (`null` if unsupported) */
|
||||
export type ChatAction =
|
||||
| ChatActionUserJoined
|
||||
|
@ -329,6 +365,10 @@ export type ChatAction =
|
|||
| ChatActionInviteLinkRevoked
|
||||
| ChatActionUserJoinedApproved
|
||||
| ChatActionTtlChanged
|
||||
| ChatActionForumToggled
|
||||
| ChatActionTopicCreated
|
||||
| ChatActionTopicEdited
|
||||
| ChatActionTopicDeleted
|
||||
| null
|
||||
|
||||
/** @internal */
|
||||
|
@ -341,12 +381,6 @@ export function _actionFromTl(
|
|||
// channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction;
|
||||
// todo - MTQ-57
|
||||
// channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector<string> new_value:Vector<string>
|
||||
// todo - MTQ-77
|
||||
// channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction;
|
||||
// channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction;
|
||||
// channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic
|
||||
// channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
|
||||
// channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic
|
||||
// todo - MTQ-72
|
||||
// channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
|
||||
// channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions
|
||||
|
@ -520,6 +554,39 @@ export function _actionFromTl(
|
|||
link: new ChatInviteLink(client, e.invite, peers),
|
||||
approvedBy: new User(client, peers.user(e.approvedBy)),
|
||||
}
|
||||
// channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction;
|
||||
// channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic
|
||||
// channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
|
||||
case 'channelAdminLogEventActionToggleForum':
|
||||
return {
|
||||
type: 'forum_toggled',
|
||||
enabled: e.newValue,
|
||||
}
|
||||
case 'channelAdminLogEventActionCreateTopic':
|
||||
assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.topic, 'forumTopic')
|
||||
|
||||
return {
|
||||
type: 'topic_created',
|
||||
topic: new ForumTopic(client, e.topic, peers),
|
||||
}
|
||||
case 'channelAdminLogEventActionEditTopic':
|
||||
assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.prevTopic, 'forumTopic')
|
||||
assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.newTopic, 'forumTopic')
|
||||
|
||||
return {
|
||||
type: 'topic_edited',
|
||||
old: new ForumTopic(client, e.prevTopic, peers),
|
||||
new: new ForumTopic(client, e.newTopic, peers),
|
||||
}
|
||||
case 'channelAdminLogEventActionDeleteTopic':
|
||||
assertTypeIs('ChannelAdminLogEventActionCreateTopic#topic', e.topic, 'forumTopic')
|
||||
|
||||
return {
|
||||
type: 'topic_deleted',
|
||||
topic: new ForumTopic(client, e.topic, peers),
|
||||
}
|
||||
// case 'channelAdminLogEventActionPinTopic'
|
||||
// ^ looks like it is not used, and pinned topics are not at all presented in the event log
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ export function normalizeChatEventFilters(input: InputChatEventFilters): ChatEve
|
|||
case 'history_toggled':
|
||||
case 'signatures_toggled':
|
||||
case 'def_perms_changed':
|
||||
case 'forum_toggled':
|
||||
serverFilter.settings = true
|
||||
break
|
||||
case 'msg_pinned':
|
||||
|
@ -94,6 +95,11 @@ export function normalizeChatEventFilters(input: InputChatEventFilters): ChatEve
|
|||
case 'invite_revoked':
|
||||
serverFilter.invites = true
|
||||
break
|
||||
case 'topic_created':
|
||||
case 'topic_edited':
|
||||
case 'topic_deleted':
|
||||
serverFilter.forums = true
|
||||
break
|
||||
default:
|
||||
assertNever(type)
|
||||
}
|
||||
|
|
202
packages/client/src/types/peers/forum-topic.ts
Normal file
202
packages/client/src/types/peers/forum-topic.ts
Normal file
|
@ -0,0 +1,202 @@
|
|||
import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { hasValueAtKey, makeInspectable } from '../../utils'
|
||||
import { MtMessageNotFoundError } from '../errors'
|
||||
import { DraftMessage, Message } from '../messages'
|
||||
import { Chat } from './chat'
|
||||
import { PeersIndex } from './peers-index'
|
||||
import { User } from './user'
|
||||
|
||||
export class ForumTopic {
|
||||
static COLOR_BLUE = 0x6fb9f0
|
||||
static COLOR_YELLOW = 0xffd67e
|
||||
static COLOR_PURPLE = 0xcb86db
|
||||
static COLOR_GREEN = 0x8eee98
|
||||
static COLOR_PINK = 0xff93b2
|
||||
static COLOR_RED = 0xfb6f5f
|
||||
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
readonly raw: tl.RawForumTopic,
|
||||
readonly _peers: PeersIndex,
|
||||
readonly _messages?: Map<number, tl.TypeMessage>,
|
||||
) {}
|
||||
|
||||
static parseTlForumTopics(client: TelegramClient, topics: tl.messages.TypeForumTopics): ForumTopic[] {
|
||||
const peers = PeersIndex.from(topics)
|
||||
const messages = new Map<number, tl.TypeMessage>()
|
||||
|
||||
topics.messages.forEach((msg) => {
|
||||
if (!msg.peerId) return
|
||||
|
||||
messages.set(msg.id, msg)
|
||||
})
|
||||
|
||||
return topics.topics
|
||||
.filter(hasValueAtKey('_', 'forumTopic'))
|
||||
.map((it) => new ForumTopic(client, it, peers, messages))
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the topic was created by the current user
|
||||
*/
|
||||
get isMy(): boolean {
|
||||
return this.raw.my!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the topic is closed
|
||||
*/
|
||||
get isClosed(): boolean {
|
||||
return this.raw.closed!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the topic is pinned
|
||||
*/
|
||||
get isPinned(): boolean {
|
||||
return this.raw.pinned!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this constructor is a reduced version of the full topic information.
|
||||
*
|
||||
* If `true`, only {@link isMy}, {@link isClosed}, {@link id}, {@link date},
|
||||
* {@link title}, {@link iconColor}, {@link iconCustomEmoji} and {@link creator}
|
||||
* parameters will contain valid information.
|
||||
*/
|
||||
get isShort(): boolean {
|
||||
return this.raw.short!
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the topic
|
||||
*/
|
||||
get id(): number {
|
||||
return this.raw.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Date when the topic was created
|
||||
*/
|
||||
get date(): Date {
|
||||
return new Date(this.raw.date * 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the topic
|
||||
*/
|
||||
get title(): string {
|
||||
return this.raw.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Color of the topic's icon, used as a fallback
|
||||
* in case {@link iconEmoji} is not set.
|
||||
*
|
||||
* One of the static `COLOR_*` fields.
|
||||
*/
|
||||
get iconColor(): number | null {
|
||||
return this.raw.iconColor ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Emoji used as the topic's icon.
|
||||
*/
|
||||
get iconCustomEmoji(): tl.Long | null {
|
||||
return this.raw.iconEmojiId ?? null
|
||||
}
|
||||
|
||||
private _creator?: User | Chat
|
||||
/**
|
||||
* Creator of the topic
|
||||
*/
|
||||
get creator(): User | Chat {
|
||||
if (this._creator) return this._creator
|
||||
|
||||
switch (this.raw.fromId._) {
|
||||
case 'peerUser':
|
||||
return (this._creator = new User(this.client, this._peers.user(this.raw.fromId.userId)))
|
||||
case 'peerChat':
|
||||
return (this._creator = new Chat(this.client, this._peers.chat(this.raw.fromId.chatId)))
|
||||
default:
|
||||
throw new MtTypeAssertionError('ForumTopic#creator', 'peerUser | peerChat', this.raw.fromId._)
|
||||
}
|
||||
}
|
||||
|
||||
private _lastMessage?: Message
|
||||
/**
|
||||
* The latest message sent in this topic
|
||||
*/
|
||||
get lastMessage(): Message {
|
||||
if (!this._lastMessage) {
|
||||
const id = this.raw.topMessage
|
||||
|
||||
if (this._messages?.has(id)) {
|
||||
this._lastMessage = new Message(this.client, this._messages.get(id)!, this._peers)
|
||||
} else {
|
||||
throw new MtMessageNotFoundError(0, id)
|
||||
}
|
||||
}
|
||||
|
||||
return this._lastMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the last read outgoing message in this topic
|
||||
*/
|
||||
get lastReadIngoing(): number {
|
||||
return this.raw.readInboxMaxId
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the last read ingoing message in this topic
|
||||
*/
|
||||
get lastReadOutgoing(): number {
|
||||
return this.raw.readOutboxMaxId
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the last read message in this topic
|
||||
*/
|
||||
get lastRead(): number {
|
||||
return Math.max(this.raw.readOutboxMaxId, this.raw.readInboxMaxId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of unread messages in the topic
|
||||
*/
|
||||
get unreadCount(): number {
|
||||
return this.raw.unreadCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of unread mentions in the topic
|
||||
*/
|
||||
get unreadMentionsCount(): number {
|
||||
return this.raw.unreadMentionsCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of unread reactions in the topic
|
||||
*/
|
||||
get unreadReactionsCount(): number {
|
||||
return this.raw.unreadReactionsCount
|
||||
}
|
||||
|
||||
private _draftMessage?: DraftMessage
|
||||
|
||||
/**
|
||||
* Draft message in the topic
|
||||
*/
|
||||
get draftMessage(): DraftMessage | null {
|
||||
if (this._draftMessage) return this._draftMessage
|
||||
|
||||
if (!this.raw.draft || this.raw.draft._ === 'draftMessageEmpty') return null
|
||||
|
||||
return (this._draftMessage = new DraftMessage(this.client, this.raw.draft))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(ForumTopic)
|
|
@ -9,6 +9,7 @@ export * from './chat-member'
|
|||
export * from './chat-permissions'
|
||||
export * from './chat-photo'
|
||||
export * from './chat-preview'
|
||||
export * from './forum-topic'
|
||||
export * from './peers-index'
|
||||
export * from './typing-status'
|
||||
export * from './user'
|
||||
|
|
Loading…
Reference in a new issue