feat(client): openChat
method
This commit is contained in:
parent
2728166727
commit
69f59ab97e
6 changed files with 254 additions and 15 deletions
|
@ -67,6 +67,7 @@ import { joinChat } from './methods/chats/join-chat.js'
|
|||
import { kickChatMember } from './methods/chats/kick-chat-member.js'
|
||||
import { leaveChat } from './methods/chats/leave-chat.js'
|
||||
import { markChatUnread } from './methods/chats/mark-chat-unread.js'
|
||||
import { openChat } from './methods/chats/open-chat.js'
|
||||
import { reorderUsernames } from './methods/chats/reorder-usernames.js'
|
||||
import { restrictChatMember } from './methods/chats/restrict-chat-member.js'
|
||||
import { saveDraft } from './methods/chats/save-draft.js'
|
||||
|
@ -223,6 +224,8 @@ import {
|
|||
enableRps,
|
||||
getCurrentRpsIncoming,
|
||||
getCurrentRpsProcessing,
|
||||
notifyChannelClosed,
|
||||
notifyChannelOpened,
|
||||
startUpdatesLoop,
|
||||
stopUpdatesLoop,
|
||||
} from './methods/updates/manager.js'
|
||||
|
@ -274,6 +277,7 @@ import {
|
|||
ForumTopic,
|
||||
GameHighScore,
|
||||
HistoryReadUpdate,
|
||||
InlineCallbackQuery,
|
||||
InlineQuery,
|
||||
InputChatEventFilters,
|
||||
InputDialogFolder,
|
||||
|
@ -415,6 +419,13 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param handler Callback query handler
|
||||
*/
|
||||
on(name: 'callback_query', handler: (upd: CallbackQuery) => void): this
|
||||
/**
|
||||
* Register an inline callback query handler
|
||||
*
|
||||
* @param name Event name
|
||||
* @param handler Inline callback query handler
|
||||
*/
|
||||
on(name: 'inline_callback_query', handler: (upd: InlineCallbackQuery) => void): this
|
||||
/**
|
||||
* Register a poll update handler
|
||||
*
|
||||
|
@ -1661,6 +1672,17 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param chatId Chat ID
|
||||
*/
|
||||
markChatUnread(chatId: InputPeerLike): Promise<void>
|
||||
/**
|
||||
* Inform the library that the user has opened a chat.
|
||||
*
|
||||
* Some library logic depends on this, for example, the library will
|
||||
* periodically ping the server to keep the updates flowing.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @param chat Chat to open
|
||||
*/
|
||||
openChat(chat: InputPeerLike): Promise<void>
|
||||
/**
|
||||
* Reorder usernames
|
||||
*
|
||||
|
@ -4863,6 +4885,36 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*
|
||||
*/
|
||||
catchUp(): void
|
||||
/**
|
||||
* **ADVANCED**
|
||||
*
|
||||
* Notify the updates manager that some channel was "opened".
|
||||
* Channel difference for "opened" channels will be fetched on a regular basis.
|
||||
* This is a low-level method, prefer using {@link openChat} instead.
|
||||
*
|
||||
* Channel must be resolve-able with `resolvePeer` method (i.e. be in cache);
|
||||
* base chat PTS must either be passed (e.g. from {@link Dialog}), or cached in storage.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @param channelId Bare ID of the channel
|
||||
* @param pts PTS of the channel, if known (e.g. from {@link Dialog})
|
||||
* @returns `true` if the channel was opened for the first time, `false` if it is already opened
|
||||
*/
|
||||
notifyChannelOpened(channelId: number, pts?: number): boolean
|
||||
/**
|
||||
* **ADVANCED**
|
||||
*
|
||||
* Notify the updates manager that some channel was "closed".
|
||||
* Basically the opposite of {@link notifyChannelOpened}.
|
||||
* This is a low-level method, prefer using {@link closeChat} instead.
|
||||
*
|
||||
* **Available**: ✅ both users and bots
|
||||
*
|
||||
* @param channelId Bare channel ID
|
||||
* @returns `true` if the chat was closed for the last time, `false` otherwise
|
||||
*/
|
||||
notifyChannelClosed(channelId: number): boolean
|
||||
/**
|
||||
* Block a user
|
||||
*
|
||||
|
@ -5377,6 +5429,10 @@ TelegramClient.prototype.markChatUnread = function (...args) {
|
|||
return markChatUnread(this, ...args)
|
||||
}
|
||||
|
||||
TelegramClient.prototype.openChat = function (...args) {
|
||||
return openChat(this, ...args)
|
||||
}
|
||||
|
||||
TelegramClient.prototype.reorderUsernames = function (...args) {
|
||||
return reorderUsernames(this, ...args)
|
||||
}
|
||||
|
@ -6037,6 +6093,14 @@ TelegramClient.prototype.catchUp = function (...args) {
|
|||
return catchUp(this, ...args)
|
||||
}
|
||||
|
||||
TelegramClient.prototype.notifyChannelOpened = function (...args) {
|
||||
return notifyChannelOpened(this, ...args)
|
||||
}
|
||||
|
||||
TelegramClient.prototype.notifyChannelClosed = function (...args) {
|
||||
return notifyChannelClosed(this, ...args)
|
||||
}
|
||||
|
||||
TelegramClient.prototype.blockUser = function (...args) {
|
||||
return blockUser(this, ...args)
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
ForumTopic,
|
||||
GameHighScore,
|
||||
HistoryReadUpdate,
|
||||
InlineCallbackQuery,
|
||||
InlineQuery,
|
||||
InputChatEventFilters,
|
||||
InputDialogFolder,
|
||||
|
|
50
packages/client/src/methods/chats/open-chat.ts
Normal file
50
packages/client/src/methods/chats/open-chat.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { BaseTelegramClient } from '@mtcute/core'
|
||||
|
||||
import { InputPeerLike } from '../../types/peers/index.js'
|
||||
import { isInputPeerChannel } from '../../utils/peer-utils.js'
|
||||
import { getPeerDialogs } from '../dialogs/get-peer-dialogs.js'
|
||||
import { notifyChannelClosed, notifyChannelOpened } from '../updates/manager.js'
|
||||
import { resolvePeer } from '../users/resolve-peer.js'
|
||||
|
||||
/**
|
||||
* Inform the library that the user has opened a chat.
|
||||
*
|
||||
* Some library logic depends on this, for example, the library will
|
||||
* periodically ping the server to keep the updates flowing.
|
||||
*
|
||||
* @param chat Chat to open
|
||||
*/
|
||||
export async function openChat(client: BaseTelegramClient, chat: InputPeerLike): Promise<void> {
|
||||
const peer = await resolvePeer(client, chat)
|
||||
|
||||
if (isInputPeerChannel(peer)) {
|
||||
const [dialog] = await getPeerDialogs(client, peer)
|
||||
|
||||
if (!client.network.params.disableUpdates) {
|
||||
notifyChannelOpened(client, peer.channelId, dialog.raw.pts)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: once we have proper dialogs/peers db, we should also
|
||||
// update full info here and fetch auxillary info (like channel members etc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the library that the user has closed a chat.
|
||||
* Un-does the effect of {@link openChat}.
|
||||
*
|
||||
* Some library logic depends on this, for example, the library will
|
||||
* periodically ping the server to keep the updates flowing.
|
||||
*
|
||||
* @param chat Chat to open
|
||||
*/
|
||||
export async function closeChat(client: BaseTelegramClient, chat: InputPeerLike): Promise<void> {
|
||||
const peer = await resolvePeer(client, chat)
|
||||
|
||||
if (isInputPeerChannel(peer) && !client.network.params.disableUpdates) {
|
||||
notifyChannelClosed(client, peer.channelId)
|
||||
}
|
||||
|
||||
// todo: once we have proper dialogs/peers db, we should also
|
||||
// update full info here and fetch auxillary info (like channel members etc)
|
||||
}
|
|
@ -5,6 +5,7 @@ import { getBarePeerId, getMarkedPeerId, markedPeerIdToBare, toggleChannelIdMark
|
|||
import { PeersIndex } from '../../types/index.js'
|
||||
import { isInputPeerChannel, isInputPeerUser, toInputChannel, toInputUser } from '../../utils/peer-utils.js'
|
||||
import { RpsMeter } from '../../utils/rps-meter.js'
|
||||
import { createDummyUpdatesContainer } from '../../utils/updates-utils.js'
|
||||
import { getAuthState } from '../auth/_state.js'
|
||||
import { _getChannelsBatched, _getUsersBatched } from '../chats/batched-queries.js'
|
||||
import { resolvePeer } from '../users/resolve-peer.js'
|
||||
|
@ -200,6 +201,11 @@ export function stopUpdatesLoop(client: BaseTelegramClient): void {
|
|||
const state = getState(client)
|
||||
if (!state.updatesLoopActive) return
|
||||
|
||||
for (const timer of state.channelDiffTimeouts.values()) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
state.channelDiffTimeouts.clear()
|
||||
|
||||
state.updatesLoopActive = false
|
||||
state.pendingUpdateContainers.clear()
|
||||
state.pendingUnorderedUpdates.clear()
|
||||
|
@ -227,6 +233,81 @@ export function catchUp(client: BaseTelegramClient): void {
|
|||
handleUpdate(state, { _: 'updatesTooLong' })
|
||||
}
|
||||
|
||||
/**
|
||||
* **ADVANCED**
|
||||
*
|
||||
* Notify the updates manager that some channel was "opened".
|
||||
* Channel difference for "opened" channels will be fetched on a regular basis.
|
||||
* This is a low-level method, prefer using {@link openChat} instead.
|
||||
*
|
||||
* Channel must be resolve-able with `resolvePeer` method (i.e. be in cache);
|
||||
* base chat PTS must either be passed (e.g. from {@link Dialog}), or cached in storage.
|
||||
*
|
||||
* @param channelId Bare ID of the channel
|
||||
* @param pts PTS of the channel, if known (e.g. from {@link Dialog})
|
||||
* @returns `true` if the channel was opened for the first time, `false` if it is already opened
|
||||
*/
|
||||
export function notifyChannelOpened(client: BaseTelegramClient, channelId: number, pts?: number): boolean {
|
||||
// this method is intentionally very dumb to avoid making this file even more unreadable
|
||||
|
||||
const state = getState(client)
|
||||
|
||||
if (!state) {
|
||||
throw new MtArgumentError('Updates processing is not enabled, use enableUpdatesProcessing() first')
|
||||
}
|
||||
|
||||
if (state.channelsOpened.has(channelId)) {
|
||||
state.log.debug('channel %d opened again', channelId)
|
||||
state.channelsOpened.set(channelId, state.channelsOpened.get(channelId)! + 1)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
state.channelsOpened.set(channelId, 1)
|
||||
state.log.debug('channel %d opened (pts=%d)', channelId, pts)
|
||||
|
||||
// force fetch channel difference
|
||||
fetchChannelDifferenceViaUpdate(state, channelId, pts)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* **ADVANCED**
|
||||
*
|
||||
* Notify the updates manager that some channel was "closed".
|
||||
* Basically the opposite of {@link notifyChannelOpened}.
|
||||
* This is a low-level method, prefer using {@link closeChat} instead.
|
||||
*
|
||||
* @param channelId Bare channel ID
|
||||
* @returns `true` if the chat was closed for the last time, `false` otherwise
|
||||
*/
|
||||
export function notifyChannelClosed(client: BaseTelegramClient, channelId: number): boolean {
|
||||
const state = getState(client)
|
||||
|
||||
if (!state) {
|
||||
throw new MtArgumentError('Updates processing is not enabled, use enableUpdatesProcessing() first')
|
||||
}
|
||||
|
||||
const opened = state.channelsOpened.get(channelId)!
|
||||
|
||||
if (opened === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (opened > 1) {
|
||||
state.log.debug('channel %d closed, but is opened %d more times', channelId, opened - 1)
|
||||
state.channelsOpened.set(channelId, opened - 1)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
state.channelsOpened.delete(channelId)
|
||||
state.log.debug('channel %d closed', channelId)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
////////////////////////////////////////////// IMPLEMENTATION //////////////////////////////////////////////
|
||||
|
||||
const STATE_SYMBOL = Symbol('updatesState')
|
||||
|
@ -670,6 +751,12 @@ async function fetchChannelDifference(
|
|||
channelId: number,
|
||||
fallbackPts?: number,
|
||||
): Promise<boolean> {
|
||||
// clear timeout if any
|
||||
if (state.channelDiffTimeouts.has(channelId)) {
|
||||
clearTimeout(state.channelDiffTimeouts.get(channelId))
|
||||
state.channelDiffTimeouts.delete(channelId)
|
||||
}
|
||||
|
||||
let _pts: number | null | undefined = state.cpts.get(channelId)
|
||||
|
||||
if (!_pts && state.catchUpChannels) {
|
||||
|
@ -700,6 +787,8 @@ async function fetchChannelDifference(
|
|||
limit = 1
|
||||
}
|
||||
|
||||
let lastTimeout = 0
|
||||
|
||||
for (;;) {
|
||||
const diff = await client.call({
|
||||
_: 'updates.getChannelDifference',
|
||||
|
@ -710,6 +799,8 @@ async function fetchChannelDifference(
|
|||
filter: { _: 'channelMessagesFilterEmpty' },
|
||||
})
|
||||
|
||||
if (diff.timeout) lastTimeout = diff.timeout
|
||||
|
||||
if (diff._ === 'updates.channelDifferenceEmpty') {
|
||||
state.log.debug('getChannelDifference (cid = %d) returned channelDifferenceEmpty', channelId)
|
||||
break
|
||||
|
@ -785,6 +876,15 @@ async function fetchChannelDifference(
|
|||
state.cpts.set(channelId, pts)
|
||||
state.cptsMod.set(channelId, pts)
|
||||
|
||||
// schedule next fetch
|
||||
if (lastTimeout !== 0 && state.channelsOpened.has(channelId)) {
|
||||
state.log.debug('scheduling next fetch for channel %d in %d seconds', channelId, lastTimeout)
|
||||
state.channelDiffTimeouts.set(
|
||||
channelId,
|
||||
setTimeout(() => fetchChannelDifferenceViaUpdate(state, channelId), lastTimeout * 1000),
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -814,6 +914,19 @@ function fetchChannelDifferenceLater(
|
|||
}
|
||||
}
|
||||
|
||||
function fetchChannelDifferenceViaUpdate(state: UpdatesState, channelId: number, pts?: number): void {
|
||||
handleUpdate(
|
||||
state,
|
||||
createDummyUpdatesContainer([
|
||||
{
|
||||
_: 'updateChannelTooLong',
|
||||
channelId,
|
||||
pts,
|
||||
},
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchDifference(
|
||||
client: BaseTelegramClient,
|
||||
state: UpdatesState,
|
||||
|
|
|
@ -128,6 +128,8 @@ export interface UpdatesState {
|
|||
|
||||
cpts: Map<number, number>
|
||||
cptsMod: Map<number, number>
|
||||
channelDiffTimeouts: Map<number, NodeJS.Timeout>
|
||||
channelsOpened: Map<number, number>
|
||||
|
||||
log: Logger
|
||||
stop: () => void
|
||||
|
@ -175,6 +177,8 @@ export function createUpdatesState(
|
|||
catchUpOnStart: opts.catchUp ?? false,
|
||||
cpts: new Map(),
|
||||
cptsMod: new Map(),
|
||||
channelDiffTimeouts: new Map(),
|
||||
channelsOpened: new Map(),
|
||||
log: client.log.create('updates'),
|
||||
stop: () => {}, // will be set later
|
||||
handler: opts.onUpdate,
|
||||
|
|
|
@ -3,6 +3,20 @@ import { MtTypeAssertionError, tl } from '@mtcute/core'
|
|||
// dummy updates which are used for methods that return messages.affectedHistory.
|
||||
// that is not an update, but it carries info about pts, and we need to handle it
|
||||
|
||||
/**
|
||||
* Create a dummy `updates` container with given updates.
|
||||
*/
|
||||
export function createDummyUpdatesContainer(updates: tl.TypeUpdate[], seq = 0): tl.TypeUpdates {
|
||||
return {
|
||||
_: 'updates',
|
||||
seq,
|
||||
date: 0,
|
||||
chats: [],
|
||||
users: [],
|
||||
updates,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy update from PTS and PTS count.
|
||||
*
|
||||
|
@ -11,21 +25,14 @@ import { MtTypeAssertionError, tl } from '@mtcute/core'
|
|||
* @param channelId Channel ID (bare), if applicable
|
||||
*/
|
||||
export function createDummyUpdate(pts: number, ptsCount: number, channelId = 0): tl.TypeUpdates {
|
||||
return {
|
||||
_: 'updates',
|
||||
seq: 0,
|
||||
date: 0,
|
||||
chats: [],
|
||||
users: [],
|
||||
updates: [
|
||||
{
|
||||
_: 'mtcute.dummyUpdate',
|
||||
channelId,
|
||||
pts,
|
||||
ptsCount,
|
||||
},
|
||||
],
|
||||
}
|
||||
return createDummyUpdatesContainer([
|
||||
{
|
||||
_: 'mtcute.dummyUpdate',
|
||||
channelId,
|
||||
pts,
|
||||
ptsCount,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
|
Loading…
Reference in a new issue