feat: stories and boosts
closes MTQ-51
This commit is contained in:
parent
62815d26d7
commit
7abcc6188a
71 changed files with 3327 additions and 257 deletions
|
@ -10,7 +10,8 @@
|
|||
"test": "mocha -r ts-node/register \"tests/**/*.spec.ts\"",
|
||||
"docs": "typedoc",
|
||||
"build": "tsc",
|
||||
"gen-client": "node ./scripts/generate-client.js"
|
||||
"gen-client": "node ./scripts/generate-client.js",
|
||||
"gen-updates": "node ./scripts/generate-updates.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "18.16.0",
|
||||
|
|
|
@ -15,3 +15,5 @@ bot_stopped = BotStoppedUpdate
|
|||
bot_chat_join_request = BotChatJoinRequestUpdate
|
||||
chat_join_request = ChatJoinRequestUpdate
|
||||
pre_checkout_query = PreCheckoutQuery
|
||||
story: StoryUpdate = StoryUpdate
|
||||
delete_story = DeleteStoryUpdate
|
|
@ -165,6 +165,7 @@ import { translateText } from './methods/messages/translate-text'
|
|||
import { unpinAllMessages } from './methods/messages/unpin-all-messages'
|
||||
import { unpinMessage } from './methods/messages/unpin-message'
|
||||
import { initTakeoutSession } from './methods/misc/init-takeout-session'
|
||||
import { _normalizePrivacyRules } from './methods/misc/normalize-privacy-rules'
|
||||
import {
|
||||
getParseMode,
|
||||
registerParseMode,
|
||||
|
@ -183,6 +184,33 @@ import { getInstalledStickers } from './methods/stickers/get-installed-stickers'
|
|||
import { getStickerSet } from './methods/stickers/get-sticker-set'
|
||||
import { moveStickerInSet } from './methods/stickers/move-sticker-in-set'
|
||||
import { setStickerSetThumb } from './methods/stickers/set-sticker-set-thumb'
|
||||
import { applyBoost } from './methods/stories/apply-boost'
|
||||
import { canApplyBoost, CanApplyBoostResult } from './methods/stories/can-apply-boost'
|
||||
import { canSendStory, CanSendStoryResult } from './methods/stories/can-send-story'
|
||||
import { deleteStories } from './methods/stories/delete-stories'
|
||||
import { editStory } from './methods/stories/edit-story'
|
||||
import { _findStoryInUpdate } from './methods/stories/find-in-update'
|
||||
import { getAllStories } from './methods/stories/get-all-stories'
|
||||
import { getBoostStats } from './methods/stories/get-boost-stats'
|
||||
import { getBoosters } from './methods/stories/get-boosters'
|
||||
import { getPeerStories } from './methods/stories/get-peer-stories'
|
||||
import { getProfileStories } from './methods/stories/get-profile-stories'
|
||||
import { getStoriesById } from './methods/stories/get-stories-by-id'
|
||||
import { getStoriesInteractions } from './methods/stories/get-stories-interactions'
|
||||
import { getStoryLink } from './methods/stories/get-story-link'
|
||||
import { getStoryViewers } from './methods/stories/get-story-viewers'
|
||||
import { hideMyStoriesViews } from './methods/stories/hide-my-stories-views'
|
||||
import { incrementStoriesViews } from './methods/stories/increment-stories-views'
|
||||
import { iterAllStories } from './methods/stories/iter-all-stories'
|
||||
import { iterBoosters } from './methods/stories/iter-boosters'
|
||||
import { iterProfileStories } from './methods/stories/iter-profile-stories'
|
||||
import { iterStoryViewers } from './methods/stories/iter-story-viewers'
|
||||
import { readStories } from './methods/stories/read-stories'
|
||||
import { reportStory } from './methods/stories/report-story'
|
||||
import { sendStory } from './methods/stories/send-story'
|
||||
import { sendStoryReaction } from './methods/stories/send-story-reaction'
|
||||
import { togglePeerStoriesArchived } from './methods/stories/toggle-peer-stories-archived'
|
||||
import { toggleStoriesPinned } from './methods/stories/toggle-stories-pinned'
|
||||
import {
|
||||
_dispatchUpdate,
|
||||
_fetchUpdatesState,
|
||||
|
@ -218,8 +246,11 @@ import { setUsername } from './methods/users/set-username'
|
|||
import { unblockUser } from './methods/users/unblock-user'
|
||||
import { updateProfile } from './methods/users/update-profile'
|
||||
import {
|
||||
AllStories,
|
||||
ArrayPaginated,
|
||||
ArrayWithTotal,
|
||||
Booster,
|
||||
BoostStats,
|
||||
BotChatJoinRequestUpdate,
|
||||
BotCommands,
|
||||
BotStoppedUpdate,
|
||||
|
@ -249,6 +280,7 @@ import {
|
|||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
InputPeerLike,
|
||||
InputPrivacyRule,
|
||||
InputReaction,
|
||||
InputStickerSetItem,
|
||||
MaybeDynamic,
|
||||
|
@ -259,6 +291,7 @@ import {
|
|||
ParsedUpdate,
|
||||
PeerReaction,
|
||||
PeersIndex,
|
||||
PeerStories,
|
||||
Photo,
|
||||
Poll,
|
||||
PollUpdate,
|
||||
|
@ -271,6 +304,11 @@ import {
|
|||
StickerSet,
|
||||
StickerSourceType,
|
||||
StickerType,
|
||||
StoriesStealthMode,
|
||||
Story,
|
||||
StoryInteractions,
|
||||
StoryViewer,
|
||||
StoryViewersList,
|
||||
TakeoutSession,
|
||||
TermsOfService,
|
||||
TypingStatus,
|
||||
|
@ -3832,6 +3870,12 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
* @param params Takeout session parameters
|
||||
*/
|
||||
initTakeoutSession(params: Omit<tl.account.RawInitTakeoutSessionRequest, '_'>): Promise<TakeoutSession>
|
||||
/**
|
||||
* Normalize {@link InputPrivacyRule}[] to `tl.TypeInputPrivacyRule`,
|
||||
* resolving the peers if needed.
|
||||
*
|
||||
*/
|
||||
_normalizePrivacyRules(rules: InputPrivacyRule[]): Promise<tl.TypeInputPrivacyRule[]>
|
||||
/**
|
||||
* Register a given {@link IMessageEntityParser} as a parse mode
|
||||
* for messages. When this method is first called, given parse
|
||||
|
@ -4079,6 +4123,532 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
): Promise<StickerSet>
|
||||
/**
|
||||
* Boost a given channel
|
||||
*
|
||||
* @param peerId Peer ID to boost
|
||||
*/
|
||||
applyBoost(peerId: InputPeerLike): Promise<void>
|
||||
/**
|
||||
* Check if the current user can apply boost to a given channel
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @returns
|
||||
* - `{ can: true }` if the user can apply boost
|
||||
* - `.current` - {@link Chat} that the current user is currently boosting, if any
|
||||
* - `{ can: false }` if the user can't apply boost
|
||||
* - `.reason == "already_boosting"` if the user is already boosting this channel
|
||||
* - `.reason == "need_premium"` if the user needs Premium to boost this channel
|
||||
*/
|
||||
canApplyBoost(peerId: InputPeerLike): Promise<CanApplyBoostResult>
|
||||
/**
|
||||
* Check if the current user can post stories as a given peer
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @returns
|
||||
* - `true` if the user can post stories
|
||||
* - `"need_admin"` if the user is not an admin in the chat
|
||||
* - `"need_boosts"` if the channel doesn't have enough boosts
|
||||
*/
|
||||
canSendStory(peerId: InputPeerLike): Promise<CanSendStoryResult>
|
||||
/**
|
||||
* Delete a story
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
*/
|
||||
deleteStories(params: {
|
||||
/**
|
||||
* Story IDs to delete
|
||||
*/
|
||||
ids: MaybeArray<number>
|
||||
|
||||
/**
|
||||
* Peer ID whose stories to delete
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
}): Promise<number[]>
|
||||
/**
|
||||
* Edit a sent story
|
||||
*
|
||||
* @returns Edited story
|
||||
*/
|
||||
editStory(params: {
|
||||
/**
|
||||
* Story ID to edit
|
||||
*/
|
||||
id: number
|
||||
|
||||
/**
|
||||
* Peer ID to whose story to edit
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Media contained in a story. Currently can only be a photo or a video.
|
||||
*/
|
||||
media?: InputMediaLike
|
||||
|
||||
/**
|
||||
* Override caption for {@link media}
|
||||
*/
|
||||
caption?: string | FormattedString<string>
|
||||
|
||||
/**
|
||||
* Override entities for {@link media}
|
||||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* Interactive elements to add to the story
|
||||
*/
|
||||
interactiveElements?: tl.TypeMediaArea[]
|
||||
|
||||
/**
|
||||
* Privacy rules to apply to the story
|
||||
*
|
||||
* @default "Everyone"
|
||||
*/
|
||||
privacyRules?: InputPrivacyRule[]
|
||||
}): Promise<Story>
|
||||
|
||||
_findStoryInUpdate(res: tl.TypeUpdates): Story
|
||||
/**
|
||||
* Get all stories (e.g. to load the top bar)
|
||||
*
|
||||
*/
|
||||
getAllStories(params?: {
|
||||
/**
|
||||
* Offset from which to fetch stories
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Whether to fetch stories from "archived" (or "hidden") peers
|
||||
*/
|
||||
archived?: boolean
|
||||
}): Promise<AllStories>
|
||||
/**
|
||||
* Get information about boosts in a channel
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
*/
|
||||
getBoostStats(peerId: InputPeerLike): Promise<BoostStats>
|
||||
/**
|
||||
* Get boosters of a channel
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
*/
|
||||
getBoosters(
|
||||
peerId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Offset for pagination
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of boosters to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<Booster, string>>
|
||||
/**
|
||||
* Get stories of a given peer
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
*/
|
||||
getPeerStories(peerId: InputPeerLike): Promise<PeerStories>
|
||||
/**
|
||||
* Get profile stories
|
||||
*
|
||||
*/
|
||||
getProfileStories(
|
||||
peerId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Kind of stories to fetch
|
||||
* - `pinned` - stories pinned to the profile and visible to everyone
|
||||
* - `archived` - "archived" stories that can later be pinned, only visible to the owner
|
||||
*
|
||||
* @default `pinned`
|
||||
*/
|
||||
kind?: 'pinned' | 'archived'
|
||||
|
||||
/**
|
||||
* Offset ID for pagination
|
||||
*/
|
||||
offsetId?: number
|
||||
|
||||
/**
|
||||
* Maximum number of stories to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<Story, number>>
|
||||
/**
|
||||
* Get a single story by its ID
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @param storyId Story ID
|
||||
*/
|
||||
getStoriesById(peerId: InputPeerLike, storyId: number): Promise<Story>
|
||||
/**
|
||||
* Get multiple stories by their IDs
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @param storyIds Story IDs
|
||||
*/
|
||||
getStoriesById(peerId: InputPeerLike, storyIds: number[]): Promise<Story[]>
|
||||
/**
|
||||
* Get brief information about story interactions.
|
||||
*
|
||||
*/
|
||||
getStoriesInteractions(peerId: InputPeerLike, storyId: number): Promise<StoryInteractions>
|
||||
/**
|
||||
* Get brief information about stories interactions.
|
||||
*
|
||||
* The result will be in the same order as the input IDs
|
||||
*
|
||||
*/
|
||||
getStoriesInteractions(peerId: InputPeerLike, storyIds: number[]): Promise<StoryInteractions[]>
|
||||
/**
|
||||
* Generate a link to a story.
|
||||
*
|
||||
* Basically the link format is `t.me/<username>/s/<story_id>`,
|
||||
* and if the user doesn't have a username, `USER_PUBLIC_MISSING` is thrown.
|
||||
*
|
||||
* I have no idea why is this an RPC call, but whatever
|
||||
*
|
||||
*/
|
||||
getStoryLink(peerId: InputPeerLike, storyId: number): Promise<string>
|
||||
/**
|
||||
* Get viewers list of a story
|
||||
*
|
||||
*/
|
||||
getStoryViewers(
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
params?: {
|
||||
/**
|
||||
* Whether to only fetch viewers from contacts
|
||||
*/
|
||||
onlyContacts?: boolean
|
||||
|
||||
/**
|
||||
* How to sort the results?
|
||||
* - `reaction` - by reaction (viewers who has reacted are first), then by date (newest first)
|
||||
* - `date` - by date, newest first
|
||||
*
|
||||
* @default `reaction`
|
||||
*/
|
||||
sortBy?: 'reaction' | 'date'
|
||||
|
||||
/**
|
||||
* Search query
|
||||
*/
|
||||
query?: string
|
||||
|
||||
/**
|
||||
* Offset ID for pagination
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of viewers to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<StoryViewersList>
|
||||
/**
|
||||
* Hide own stories views (activate so called "stealth mode")
|
||||
*
|
||||
* Currently has a cooldown of 1 hour, and throws FLOOD_WAIT error if it is on cooldown.
|
||||
*
|
||||
*/
|
||||
hideMyStoriesViews(params?: {
|
||||
/**
|
||||
* Whether to hide views from the last 5 minutes
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
past?: boolean
|
||||
|
||||
/**
|
||||
* Whether to hide views for the next 25 minutes
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
future?: boolean
|
||||
}): Promise<StoriesStealthMode>
|
||||
/**
|
||||
* Increment views of one or more stories.
|
||||
*
|
||||
* This should be used for pinned stories, as they can't
|
||||
* be marked as read when the user sees them ({@link Story#isActive} == false)
|
||||
*
|
||||
* @param peerId Peer ID whose stories to mark as read
|
||||
* @param ids ID(s) of the stories to increment views of (max 200)
|
||||
*/
|
||||
incrementStoriesViews(peerId: InputPeerLike, ids: MaybeArray<number>): Promise<boolean>
|
||||
/**
|
||||
* Iterate over all stories (e.g. to load the top bar)
|
||||
*
|
||||
* Wrapper over {@link getAllStories}
|
||||
*
|
||||
*/
|
||||
iterAllStories(params?: {
|
||||
/**
|
||||
* Offset from which to start fetching stories
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of stories to fetch
|
||||
*
|
||||
* @default Infinity
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Whether to fetch stories from "archived" (or "hidden") peers
|
||||
*/
|
||||
archived?: boolean
|
||||
}): AsyncIterableIterator<PeerStories>
|
||||
/**
|
||||
* Iterate over boosters of a channel.
|
||||
*
|
||||
* Wrapper over {@link getBoosters}
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
*/
|
||||
iterBoosters(
|
||||
peerId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getBoosters']>[1] & {
|
||||
/**
|
||||
* Total number of boosters to fetch
|
||||
*
|
||||
* @default Infinity, i.e. fetch all boosters
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of boosters to fetch per request
|
||||
* Usually you don't need to change this
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<Booster>
|
||||
/**
|
||||
* Iterate over profile stories. Wrapper over {@link getProfileStories}
|
||||
*
|
||||
*/
|
||||
iterProfileStories(
|
||||
peerId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getProfileStories']>[1] & {
|
||||
/**
|
||||
* Total number of stories to fetch
|
||||
*
|
||||
* @default `Infinity`, i.e. fetch all stories
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of stories to fetch per request.
|
||||
* Usually you shouldn't care about this.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<Story>
|
||||
/**
|
||||
* Iterate over viewers list of a story.
|
||||
* Wrapper over {@link getStoryViewers}
|
||||
*
|
||||
*/
|
||||
iterStoryViewers(
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
params?: Parameters<TelegramClient['getStoryViewers']>[2] & {
|
||||
/**
|
||||
* Total number of viewers to fetch
|
||||
*
|
||||
* @default Infinity, i.e. fetch all viewers
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of viewers to fetch per request.
|
||||
* Usually you don't need to change this.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<StoryViewer>
|
||||
/**
|
||||
* Mark all stories up to a given ID as read
|
||||
*
|
||||
* This should only be used for "active" stories ({@link Story#isActive} == false)
|
||||
*
|
||||
* @param peerId Peer ID whose stories to mark as read
|
||||
* @returns IDs of the stores that were marked as read
|
||||
*/
|
||||
readStories(peerId: InputPeerLike, maxId: number): Promise<number[]>
|
||||
/**
|
||||
* Report a story (or multiple stories) to the moderation team
|
||||
*
|
||||
*/
|
||||
reportStory(
|
||||
peerId: InputPeerLike,
|
||||
storyIds: MaybeArray<number>,
|
||||
params?: {
|
||||
/**
|
||||
* Reason for reporting
|
||||
*
|
||||
* @default inputReportReasonSpam
|
||||
*/
|
||||
reason?: tl.TypeReportReason
|
||||
|
||||
/**
|
||||
* Additional comment to the report
|
||||
*/
|
||||
message?: string
|
||||
},
|
||||
): Promise<void>
|
||||
/**
|
||||
* Send (or remove) a reaction to a story
|
||||
*
|
||||
*/
|
||||
sendStoryReaction(
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
reaction: InputReaction,
|
||||
params?: {
|
||||
/**
|
||||
* Whether to add this reaction to recently used
|
||||
*/
|
||||
addToRecent?: boolean
|
||||
},
|
||||
): Promise<void>
|
||||
/**
|
||||
* Send a story
|
||||
*
|
||||
* @returns Created story
|
||||
*/
|
||||
sendStory(params: {
|
||||
/**
|
||||
* Peer ID to send story as
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Media contained in a story. Currently can only be a photo or a video.
|
||||
*
|
||||
* You can also pass TDLib and Bot API compatible File ID,
|
||||
* which will be wrapped in {@link InputMedia.auto}
|
||||
*/
|
||||
media: InputMediaLike | string
|
||||
|
||||
/**
|
||||
* Override caption for {@link media}
|
||||
*/
|
||||
caption?: string | FormattedString<string>
|
||||
|
||||
/**
|
||||
* Override entities for {@link media}
|
||||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* Whether to automatically pin this story to the profile
|
||||
*/
|
||||
pinned?: boolean
|
||||
|
||||
/**
|
||||
* Whether to disallow sharing this story
|
||||
*/
|
||||
forbidForwards?: boolean
|
||||
|
||||
/**
|
||||
* Interactive elements to add to the story
|
||||
*/
|
||||
interactiveElements?: tl.TypeMediaArea[]
|
||||
|
||||
/**
|
||||
* Privacy rules to apply to the story
|
||||
*
|
||||
* @default "Everyone"
|
||||
*/
|
||||
privacyRules?: InputPrivacyRule[]
|
||||
|
||||
/**
|
||||
* TTL period of the story, in seconds
|
||||
*
|
||||
* @default 86400
|
||||
*/
|
||||
period?: number
|
||||
}): Promise<Story>
|
||||
/**
|
||||
* Toggle whether peer's stories are archived (hidden) or not.
|
||||
*
|
||||
* This **does not** archive the chat with that peer, only stories.
|
||||
*
|
||||
*/
|
||||
togglePeerStoriesArchived(peerId: InputPeerLike, archived: boolean): Promise<void>
|
||||
/**
|
||||
* Toggle one or more stories pinned status
|
||||
*
|
||||
* @returns IDs of stories that were toggled
|
||||
*/
|
||||
toggleStoriesPinned(params: {
|
||||
/**
|
||||
* Story ID(s) to toggle
|
||||
*/
|
||||
ids: MaybeArray<number>
|
||||
|
||||
/**
|
||||
* Whether to pin or unpin the story
|
||||
*/
|
||||
pinned: boolean
|
||||
|
||||
/**
|
||||
* Peer ID whose stories to toggle
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
}): Promise<number[]>
|
||||
/**
|
||||
* Enable RPS meter.
|
||||
* Only available in NodeJS v10.7.0 and newer
|
||||
|
@ -4579,6 +5149,7 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
unpinAllMessages = unpinAllMessages
|
||||
unpinMessage = unpinMessage
|
||||
initTakeoutSession = initTakeoutSession
|
||||
_normalizePrivacyRules = _normalizePrivacyRules
|
||||
registerParseMode = registerParseMode
|
||||
unregisterParseMode = unregisterParseMode
|
||||
getParseMode = getParseMode
|
||||
|
@ -4597,6 +5168,33 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
getStickerSet = getStickerSet
|
||||
moveStickerInSet = moveStickerInSet
|
||||
setStickerSetThumb = setStickerSetThumb
|
||||
applyBoost = applyBoost
|
||||
canApplyBoost = canApplyBoost
|
||||
canSendStory = canSendStory
|
||||
deleteStories = deleteStories
|
||||
editStory = editStory
|
||||
_findStoryInUpdate = _findStoryInUpdate
|
||||
getAllStories = getAllStories
|
||||
getBoostStats = getBoostStats
|
||||
getBoosters = getBoosters
|
||||
getPeerStories = getPeerStories
|
||||
getProfileStories = getProfileStories
|
||||
getStoriesById = getStoriesById
|
||||
getStoriesInteractions = getStoriesInteractions
|
||||
getStoryLink = getStoryLink
|
||||
getStoryViewers = getStoryViewers
|
||||
hideMyStoriesViews = hideMyStoriesViews
|
||||
incrementStoriesViews = incrementStoriesViews
|
||||
iterAllStories = iterAllStories
|
||||
iterBoosters = iterBoosters
|
||||
iterProfileStories = iterProfileStories
|
||||
iterStoryViewers = iterStoryViewers
|
||||
readStories = readStories
|
||||
reportStory = reportStory
|
||||
sendStoryReaction = sendStoryReaction
|
||||
sendStory = sendStory
|
||||
togglePeerStoriesArchived = togglePeerStoriesArchived
|
||||
toggleStoriesPinned = toggleStoriesPinned
|
||||
enableRps = enableRps
|
||||
getCurrentRpsIncoming = getCurrentRpsIncoming
|
||||
getCurrentRpsProcessing = getCurrentRpsProcessing
|
||||
|
|
|
@ -11,8 +11,11 @@ import { tdFileId } from '@mtcute/file-id'
|
|||
|
||||
// @copy
|
||||
import {
|
||||
AllStories,
|
||||
ArrayPaginated,
|
||||
ArrayWithTotal,
|
||||
Booster,
|
||||
BoostStats,
|
||||
BotChatJoinRequestUpdate,
|
||||
BotCommands,
|
||||
BotStoppedUpdate,
|
||||
|
@ -42,6 +45,7 @@ import {
|
|||
InputInlineResult,
|
||||
InputMediaLike,
|
||||
InputPeerLike,
|
||||
InputPrivacyRule,
|
||||
InputReaction,
|
||||
InputStickerSetItem,
|
||||
MaybeDynamic,
|
||||
|
@ -52,6 +56,7 @@ import {
|
|||
ParsedUpdate,
|
||||
PeerReaction,
|
||||
PeersIndex,
|
||||
PeerStories,
|
||||
Photo,
|
||||
Poll,
|
||||
PollUpdate,
|
||||
|
@ -64,6 +69,11 @@ import {
|
|||
StickerSet,
|
||||
StickerSourceType,
|
||||
StickerType,
|
||||
StoriesStealthMode,
|
||||
Story,
|
||||
StoryInteractions,
|
||||
StoryViewer,
|
||||
StoryViewersList,
|
||||
TakeoutSession,
|
||||
TermsOfService,
|
||||
TypingStatus,
|
||||
|
|
|
@ -253,7 +253,7 @@ export async function* iterDialogs(
|
|||
const last = dialogs[dialogs.length - 1]
|
||||
offsetPeer = last.chat.inputPeer
|
||||
offsetId = last.raw.topMessage
|
||||
offsetDate = normalizeDate(last.lastMessage.date)!
|
||||
offsetDate = last.lastMessage.raw.date
|
||||
|
||||
for (const d of dialogs) {
|
||||
if (filterFolder && !filterFolder(d)) continue
|
||||
|
|
|
@ -246,14 +246,15 @@ export async function uploadFile(
|
|||
lock.release()
|
||||
}
|
||||
|
||||
if (fileSize === -1 && stream.readableEnded) {
|
||||
fileSize = pos + (part?.length ?? 0)
|
||||
partCount = ~~((fileSize + partSize - 1) / partSize)
|
||||
this.log.debug('readable ended, file size = %d, part count = %d', fileSize, partCount)
|
||||
if (!part && fileSize !== -1) {
|
||||
throw new MtArgumentError(`Unexpected EOS (there were only ${idx} parts, but expected ${partCount})`)
|
||||
}
|
||||
|
||||
if (!part) {
|
||||
throw new MtArgumentError(`Unexpected EOS (there were only ${idx} parts, but expected ${partCount})`)
|
||||
if (fileSize === -1 && (stream.readableEnded || !part)) {
|
||||
fileSize = pos + (part?.length ?? 0)
|
||||
partCount = ~~((fileSize + partSize - 1) / partSize)
|
||||
if (!part) part = Buffer.alloc(0)
|
||||
this.log.debug('readable ended, file size = %d, part count = %d', fileSize, partCount)
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(part)) {
|
||||
|
@ -305,14 +306,10 @@ export async function uploadFile(
|
|||
return uploadNextPart()
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from(
|
||||
{
|
||||
length: connectionPoolSize * requestsPerConnection,
|
||||
},
|
||||
uploadNextPart,
|
||||
),
|
||||
)
|
||||
let poolSize = connectionPoolSize * requestsPerConnection
|
||||
if (partCount !== -1 && poolSize > partCount) poolSize = partCount
|
||||
|
||||
await Promise.all(Array.from({ length: poolSize }, uploadNextPart))
|
||||
|
||||
let inputFile: tl.TypeInputFile
|
||||
|
||||
|
|
|
@ -88,7 +88,9 @@ export async function editMessage(
|
|||
params.media.entities,
|
||||
)
|
||||
}
|
||||
} else if (params.text) {
|
||||
}
|
||||
|
||||
if (params.text) {
|
||||
[content, entities] = await this._parseEntities(params.text, params.parseMode, params.entities)
|
||||
}
|
||||
|
||||
|
|
52
packages/client/src/methods/misc/normalize-privacy-rules.ts
Normal file
52
packages/client/src/methods/misc/normalize-privacy-rules.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPrivacyRule } from '../../types'
|
||||
import { normalizeToInputUser } from '../../utils'
|
||||
|
||||
/**
|
||||
* Normalize {@link InputPrivacyRule}[] to `tl.TypeInputPrivacyRule`,
|
||||
* resolving the peers if needed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function _normalizePrivacyRules(
|
||||
this: TelegramClient,
|
||||
rules: InputPrivacyRule[],
|
||||
): Promise<tl.TypeInputPrivacyRule[]> {
|
||||
const res: tl.TypeInputPrivacyRule[] = []
|
||||
|
||||
for (const rule of rules) {
|
||||
if ('_' in rule) {
|
||||
res.push(rule)
|
||||
continue
|
||||
}
|
||||
|
||||
if ('users' in rule) {
|
||||
const users = await this.resolvePeerMany(rule.users, normalizeToInputUser)
|
||||
|
||||
res.push({
|
||||
_: rule.allow ? 'inputPrivacyValueAllowUsers' : 'inputPrivacyValueDisallowUsers',
|
||||
users,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if ('chats' in rule) {
|
||||
const chats = await this.resolvePeerMany(rule.chats)
|
||||
|
||||
res.push({
|
||||
_: rule.allow ? 'inputPrivacyValueAllowChatParticipants' : 'inputPrivacyValueDisallowChatParticipants',
|
||||
chats: chats.map((peer) => {
|
||||
if ('channelId' in peer) return peer.channelId
|
||||
if ('chatId' in peer) return peer.chatId
|
||||
|
||||
throw new Error('UNREACHABLE')
|
||||
}),
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
15
packages/client/src/methods/stories/apply-boost.ts
Normal file
15
packages/client/src/methods/stories/apply-boost.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Boost a given channel
|
||||
*
|
||||
* @param peerId Peer ID to boost
|
||||
* @internal
|
||||
*/
|
||||
export async function applyBoost(this: TelegramClient, peerId: InputPeerLike): Promise<void> {
|
||||
await this.call({
|
||||
_: 'stories.applyBoost',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
})
|
||||
}
|
63
packages/client/src/methods/stories/can-apply-boost.ts
Normal file
63
packages/client/src/methods/stories/can-apply-boost.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { Chat, InputPeerLike, PeersIndex } from '../../types'
|
||||
|
||||
// @exported
|
||||
export type CanApplyBoostResult =
|
||||
| { can: true; current?: Chat }
|
||||
| { can: false; reason: 'already_boosting' | 'need_premium' }
|
||||
| { can: false; reason: 'timeout'; until: Date }
|
||||
|
||||
/**
|
||||
* Check if the current user can apply boost to a given channel
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @returns
|
||||
* - `{ can: true }` if the user can apply boost
|
||||
* - `.current` - {@link Chat} that the current user is currently boosting, if any
|
||||
* - `{ can: false }` if the user can't apply boost
|
||||
* - `.reason == "already_boosting"` if the user is already boosting this channel
|
||||
* - `.reason == "need_premium"` if the user needs Premium to boost this channel
|
||||
* - `.reason == "timeout"` if the user has recently boosted a channel and needs to wait
|
||||
* (`.until` contains the date until which the user needs to wait)
|
||||
* @internal
|
||||
*/
|
||||
export async function canApplyBoost(this: TelegramClient, peerId: InputPeerLike): Promise<CanApplyBoostResult> {
|
||||
try {
|
||||
const res = await this.call(
|
||||
{
|
||||
_: 'stories.canApplyBoost',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
},
|
||||
{ floodSleepThreshold: 0 },
|
||||
)
|
||||
|
||||
if (res._ === 'stories.canApplyBoostOk') return { can: true }
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
const chat = new Chat(this, peers.get(res.currentBoost))
|
||||
|
||||
return { can: true, current: chat }
|
||||
} catch (e) {
|
||||
if (!tl.RpcError.is(e)) throw e
|
||||
|
||||
if (e.is('BOOST_NOT_MODIFIED')) {
|
||||
return { can: false, reason: 'already_boosting' }
|
||||
}
|
||||
|
||||
if (e.is('PREMIUM_ACCOUNT_REQUIRED')) {
|
||||
return { can: false, reason: 'need_premium' }
|
||||
}
|
||||
|
||||
if (e.is('FLOOD_WAIT_%d')) {
|
||||
return {
|
||||
can: false,
|
||||
reason: 'timeout',
|
||||
until: new Date(Date.now() + e.seconds * 1000),
|
||||
}
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
39
packages/client/src/methods/stories/can-send-story.ts
Normal file
39
packages/client/src/methods/stories/can-send-story.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
// @exported
|
||||
export type CanSendStoryResult = true | 'need_admin' | 'need_boosts'
|
||||
|
||||
/**
|
||||
* Check if the current user can post stories as a given peer
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @returns
|
||||
* - `true` if the user can post stories
|
||||
* - `"need_admin"` if the user is not an admin in the chat
|
||||
* - `"need_boosts"` if the channel doesn't have enough boosts
|
||||
* @internal
|
||||
*/
|
||||
export async function canSendStory(this: TelegramClient, peerId: InputPeerLike): Promise<CanSendStoryResult> {
|
||||
try {
|
||||
const res = await this.call({
|
||||
_: 'stories.canSendStory',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
})
|
||||
if (!res) return 'need_admin'
|
||||
|
||||
return true
|
||||
} catch (e) {
|
||||
if (tl.RpcError.is(e, 'CHAT_ADMIN_REQUIRED')) {
|
||||
return 'need_admin'
|
||||
}
|
||||
|
||||
if (tl.RpcError.is(e, 'BOOSTS_REQUIRED')) {
|
||||
return 'need_boosts'
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
35
packages/client/src/methods/stories/delete-stories.ts
Normal file
35
packages/client/src/methods/stories/delete-stories.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Delete a story
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
* @internal
|
||||
*/
|
||||
export async function deleteStories(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Story IDs to delete
|
||||
*/
|
||||
ids: MaybeArray<number>
|
||||
|
||||
/**
|
||||
* Peer ID whose stories to delete
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
},
|
||||
): Promise<number[]> {
|
||||
const { ids, peer = 'me' } = params
|
||||
|
||||
return this.call({
|
||||
_: 'stories.deleteStories',
|
||||
peer: await this.resolvePeer(peer),
|
||||
id: Array.isArray(ids) ? ids : [ids],
|
||||
})
|
||||
}
|
101
packages/client/src/methods/stories/edit-story.ts
Normal file
101
packages/client/src/methods/stories/edit-story.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { FormattedString, InputMediaLike, InputPeerLike, InputPrivacyRule, Story } from '../../types'
|
||||
|
||||
/**
|
||||
* Edit a sent story
|
||||
*
|
||||
* @returns Edited story
|
||||
* @internal
|
||||
*/
|
||||
export async function editStory(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Story ID to edit
|
||||
*/
|
||||
id: number
|
||||
|
||||
/**
|
||||
* Peer ID to whose story to edit
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Media contained in a story. Currently can only be a photo or a video.
|
||||
*/
|
||||
media?: InputMediaLike
|
||||
|
||||
/**
|
||||
* Override caption for {@link media}
|
||||
*/
|
||||
caption?: string | FormattedString<string>
|
||||
|
||||
/**
|
||||
* Override entities for {@link media}
|
||||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* Interactive elements to add to the story
|
||||
*/
|
||||
interactiveElements?: tl.TypeMediaArea[]
|
||||
|
||||
/**
|
||||
* Privacy rules to apply to the story
|
||||
*
|
||||
* @default "Everyone"
|
||||
*/
|
||||
privacyRules?: InputPrivacyRule[]
|
||||
},
|
||||
): Promise<Story> {
|
||||
const { id, peer = 'me', interactiveElements } = params
|
||||
|
||||
let caption: string | undefined = undefined
|
||||
let entities: tl.TypeMessageEntity[] | undefined
|
||||
let media: tl.TypeInputMedia | undefined = undefined
|
||||
|
||||
if (params.media) {
|
||||
media = await this._normalizeInputMedia(params.media, params)
|
||||
|
||||
// if there's no caption in input media (i.e. not present or undefined),
|
||||
// user wants to keep current caption, thus `content` needs to stay `undefined`
|
||||
if ('caption' in params.media && params.media.caption !== undefined) {
|
||||
[caption, entities] = await this._parseEntities(
|
||||
params.media.caption,
|
||||
params.parseMode,
|
||||
params.media.entities,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (params.caption) {
|
||||
[caption, entities] = await this._parseEntities(params.caption, params.parseMode, params.entities)
|
||||
}
|
||||
|
||||
const privacyRules = params.privacyRules ? await this._normalizePrivacyRules(params.privacyRules) : undefined
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.editStory',
|
||||
peer: await this.resolvePeer(peer),
|
||||
id,
|
||||
media,
|
||||
mediaAreas: interactiveElements,
|
||||
caption,
|
||||
entities,
|
||||
privacyRules,
|
||||
})
|
||||
|
||||
return this._findStoryInUpdate(res)
|
||||
}
|
24
packages/client/src/methods/stories/find-in-update.ts
Normal file
24
packages/client/src/methods/stories/find-in-update.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
import { assertTypeIs, hasValueAtKey } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { PeersIndex, Story } from '../../types'
|
||||
import { assertIsUpdatesGroup } from '../../utils/updates-utils'
|
||||
|
||||
/** @internal */
|
||||
export function _findStoryInUpdate(this: TelegramClient, res: tl.TypeUpdates): Story {
|
||||
assertIsUpdatesGroup('_findStoryInUpdate', res)
|
||||
|
||||
this._handleUpdate(res, true)
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
const updateStory = res.updates.find(hasValueAtKey('_', 'updateStory'))
|
||||
|
||||
if (!updateStory) {
|
||||
throw new MtTypeAssertionError('_findStoryInUpdate (@ .updates[*])', 'updateStory', 'none')
|
||||
}
|
||||
|
||||
assertTypeIs('updateStory.story', updateStory.story, 'storyItem')
|
||||
|
||||
return new Story(this, updateStory.story, peers)
|
||||
}
|
39
packages/client/src/methods/stories/get-all-stories.ts
Normal file
39
packages/client/src/methods/stories/get-all-stories.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { assertTypeIsNot } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { AllStories } from '../../types'
|
||||
|
||||
/**
|
||||
* Get all stories (e.g. to load the top bar)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getAllStories(
|
||||
this: TelegramClient,
|
||||
params?: {
|
||||
/**
|
||||
* Offset from which to fetch stories
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Whether to fetch stories from "archived" (or "hidden") peers
|
||||
*/
|
||||
archived?: boolean
|
||||
},
|
||||
): Promise<AllStories> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { offset, archived } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.getAllStories',
|
||||
state: offset,
|
||||
next: Boolean(offset),
|
||||
hidden: archived,
|
||||
})
|
||||
|
||||
assertTypeIsNot('getAllStories', res, 'stories.allStoriesNotModified')
|
||||
|
||||
return new AllStories(this, res)
|
||||
}
|
18
packages/client/src/methods/stories/get-boost-stats.ts
Normal file
18
packages/client/src/methods/stories/get-boost-stats.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { BoostStats } from '../../types/stories/boost-stats'
|
||||
|
||||
/**
|
||||
* Get information about boosts in a channel
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
* @internal
|
||||
*/
|
||||
export async function getBoostStats(this: TelegramClient, peerId: InputPeerLike): Promise<BoostStats> {
|
||||
const res = await this.call({
|
||||
_: 'stories.getBoostsStatus',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
})
|
||||
|
||||
return new BoostStats(res)
|
||||
}
|
45
packages/client/src/methods/stories/get-boosters.ts
Normal file
45
packages/client/src/methods/stories/get-boosters.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { ArrayPaginated, InputPeerLike, PeersIndex } from '../../types'
|
||||
import { Booster } from '../../types/stories/booster'
|
||||
import { makeArrayPaginated } from '../../utils'
|
||||
|
||||
/**
|
||||
* Get boosters of a channel
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
* @internal
|
||||
*/
|
||||
export async function getBoosters(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Offset for pagination
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of boosters to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<Booster, string>> {
|
||||
const { offset = '', limit = 100 } = params ?? {}
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.getBoostersList',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
offset,
|
||||
limit,
|
||||
})
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
|
||||
return makeArrayPaginated(
|
||||
res.boosters.map((it) => new Booster(this, it, peers)),
|
||||
res.count,
|
||||
res.nextOffset,
|
||||
)
|
||||
}
|
19
packages/client/src/methods/stories/get-peer-stories.ts
Normal file
19
packages/client/src/methods/stories/get-peer-stories.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, PeersIndex, PeerStories } from '../../types'
|
||||
|
||||
/**
|
||||
* Get stories of a given peer
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @internal
|
||||
*/
|
||||
export async function getPeerStories(this: TelegramClient, peerId: InputPeerLike): Promise<PeerStories> {
|
||||
const res = await this.call({
|
||||
_: 'stories.getPeerStories',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
})
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
|
||||
return new PeerStories(this, res.stories, peers)
|
||||
}
|
60
packages/client/src/methods/stories/get-profile-stories.ts
Normal file
60
packages/client/src/methods/stories/get-profile-stories.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { ArrayPaginated, InputPeerLike, PeersIndex, Story } from '../../types'
|
||||
import { makeArrayPaginated } from '../../utils'
|
||||
|
||||
/**
|
||||
* Get profile stories
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getProfileStories(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
params?: {
|
||||
/**
|
||||
* Kind of stories to fetch
|
||||
* - `pinned` - stories pinned to the profile and visible to everyone
|
||||
* - `archived` - "archived" stories that can later be pinned, only visible to the owner
|
||||
*
|
||||
* @default `pinned`
|
||||
*/
|
||||
kind?: 'pinned' | 'archived'
|
||||
|
||||
/**
|
||||
* Offset ID for pagination
|
||||
*/
|
||||
offsetId?: number
|
||||
|
||||
/**
|
||||
* Maximum number of stories to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<ArrayPaginated<Story, number>> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { kind = 'pinned', offsetId = 0, limit = 100 } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: kind === 'pinned' ? 'stories.getPinnedStories' : 'stories.getStoriesArchive',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
offsetId,
|
||||
limit,
|
||||
})
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
|
||||
const stories = res.stories.map((it) => {
|
||||
assertTypeIs('getProfileStories', it, 'storyItem')
|
||||
|
||||
return new Story(this, it, peers)
|
||||
})
|
||||
const last = stories[stories.length - 1]
|
||||
const next = last?.id
|
||||
|
||||
return makeArrayPaginated(stories, res.count, next)
|
||||
}
|
51
packages/client/src/methods/stories/get-stories-by-id.ts
Normal file
51
packages/client/src/methods/stories/get-stories-by-id.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, PeersIndex, Story } from '../../types'
|
||||
|
||||
/**
|
||||
* Get a single story by its ID
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @param storyId Story ID
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesById(this: TelegramClient, peerId: InputPeerLike, storyId: number): Promise<Story>
|
||||
|
||||
/**
|
||||
* Get multiple stories by their IDs
|
||||
*
|
||||
* @param peerId Peer ID whose stories to fetch
|
||||
* @param storyIds Story IDs
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesById(this: TelegramClient, peerId: InputPeerLike, storyIds: number[]): Promise<Story[]>
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesById(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyIds: MaybeArray<number>,
|
||||
): Promise<MaybeArray<Story>> {
|
||||
const single = !Array.isArray(storyIds)
|
||||
if (single) storyIds = [storyIds as number]
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.getStoriesByID',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
id: storyIds as number[],
|
||||
})
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
|
||||
const stories = res.stories.map((it) => {
|
||||
assertTypeIs('getProfileStories', it, 'storyItem')
|
||||
|
||||
return new Story(this, it, peers)
|
||||
})
|
||||
|
||||
return single ? stories[0] : stories
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, PeersIndex, StoryInteractions } from '../../types'
|
||||
|
||||
/**
|
||||
* Get brief information about story interactions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesInteractions(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
): Promise<StoryInteractions>
|
||||
|
||||
/**
|
||||
* Get brief information about stories interactions.
|
||||
*
|
||||
* The result will be in the same order as the input IDs
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesInteractions(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyIds: number[],
|
||||
): Promise<StoryInteractions[]>
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoriesInteractions(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyIds: MaybeArray<number>,
|
||||
): Promise<MaybeArray<StoryInteractions>> {
|
||||
const isSingle = !Array.isArray(storyIds)
|
||||
if (isSingle) storyIds = [storyIds as number]
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.getStoriesViews',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
id: storyIds as number[],
|
||||
})
|
||||
|
||||
const peers = PeersIndex.from(res)
|
||||
|
||||
const infos = res.views.map((it) => new StoryInteractions(this, it, peers))
|
||||
|
||||
return isSingle ? infos[0] : infos
|
||||
}
|
20
packages/client/src/methods/stories/get-story-link.ts
Normal file
20
packages/client/src/methods/stories/get-story-link.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Generate a link to a story.
|
||||
*
|
||||
* Basically the link format is `t.me/<username>/s/<story_id>`,
|
||||
* and if the user doesn't have a username, `USER_PUBLIC_MISSING` is thrown.
|
||||
*
|
||||
* I have no idea why is this an RPC call, but whatever
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoryLink(this: TelegramClient, peerId: InputPeerLike, storyId: number): Promise<string> {
|
||||
return this.call({
|
||||
_: 'stories.exportStoryLink',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
id: storyId,
|
||||
}).then((r) => r.link)
|
||||
}
|
62
packages/client/src/methods/stories/get-story-viewers.ts
Normal file
62
packages/client/src/methods/stories/get-story-viewers.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, StoryViewersList } from '../../types'
|
||||
|
||||
/**
|
||||
* Get viewers list of a story
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function getStoryViewers(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
params?: {
|
||||
/**
|
||||
* Whether to only fetch viewers from contacts
|
||||
*/
|
||||
onlyContacts?: boolean
|
||||
|
||||
/**
|
||||
* How to sort the results?
|
||||
* - `reaction` - by reaction (viewers who has reacted are first), then by date (newest first)
|
||||
* - `date` - by date, newest first
|
||||
*
|
||||
* @default `reaction`
|
||||
*/
|
||||
sortBy?: 'reaction' | 'date'
|
||||
|
||||
/**
|
||||
* Search query
|
||||
*/
|
||||
query?: string
|
||||
|
||||
/**
|
||||
* Offset ID for pagination
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of viewers to fetch
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
limit?: number
|
||||
},
|
||||
): Promise<StoryViewersList> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { onlyContacts, sortBy = 'reaction', query, offset = '', limit = 100 } = params
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.getStoryViewsList',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
justContacts: onlyContacts,
|
||||
reactionsFirst: sortBy === 'reaction',
|
||||
q: query,
|
||||
id: storyId,
|
||||
offset,
|
||||
limit,
|
||||
})
|
||||
|
||||
return new StoryViewersList(this, res)
|
||||
}
|
48
packages/client/src/methods/stories/hide-my-stories-views.ts
Normal file
48
packages/client/src/methods/stories/hide-my-stories-views.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { MtTypeAssertionError } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { StoriesStealthMode } from '../../types/stories/stealth-mode'
|
||||
import { assertIsUpdatesGroup, hasValueAtKey } from '../../utils'
|
||||
|
||||
/**
|
||||
* Hide own stories views (activate so called "stealth mode")
|
||||
*
|
||||
* Currently has a cooldown of 1 hour, and throws FLOOD_WAIT error if it is on cooldown.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function hideMyStoriesViews(
|
||||
this: TelegramClient,
|
||||
params?: {
|
||||
/**
|
||||
* Whether to hide views from the last 5 minutes
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
past?: boolean
|
||||
|
||||
/**
|
||||
* Whether to hide views for the next 25 minutes
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
future?: boolean
|
||||
},
|
||||
): Promise<StoriesStealthMode> {
|
||||
const { past = true, future = true } = params ?? {}
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.activateStealthMode',
|
||||
past,
|
||||
future,
|
||||
})
|
||||
|
||||
assertIsUpdatesGroup('hideMyStoriesViews', res)
|
||||
this._handleUpdate(res, true)
|
||||
|
||||
const upd = res.updates.find(hasValueAtKey('_', 'updateStoriesStealthMode'))
|
||||
|
||||
if (!upd) { throw new MtTypeAssertionError('hideMyStoriesViews (@ res.updates[*])', 'updateStoriesStealthMode', 'none') }
|
||||
|
||||
return new StoriesStealthMode(upd.stealthMode)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Increment views of one or more stories.
|
||||
*
|
||||
* This should be used for pinned stories, as they can't
|
||||
* be marked as read when the user sees them ({@link Story#isActive} == false)
|
||||
*
|
||||
* @param peerId Peer ID whose stories to mark as read
|
||||
* @param ids ID(s) of the stories to increment views of (max 200)
|
||||
* @internal
|
||||
*/
|
||||
export async function incrementStoriesViews(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
ids: MaybeArray<number>,
|
||||
): Promise<boolean> {
|
||||
return this.call({
|
||||
_: 'stories.incrementStoryViews',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
id: Array.isArray(ids) ? ids : [ids],
|
||||
})
|
||||
}
|
53
packages/client/src/methods/stories/iter-all-stories.ts
Normal file
53
packages/client/src/methods/stories/iter-all-stories.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { PeerStories } from '../../types'
|
||||
|
||||
/**
|
||||
* Iterate over all stories (e.g. to load the top bar)
|
||||
*
|
||||
* Wrapper over {@link getAllStories}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function* iterAllStories(
|
||||
this: TelegramClient,
|
||||
params?: {
|
||||
/**
|
||||
* Offset from which to start fetching stories
|
||||
*/
|
||||
offset?: string
|
||||
|
||||
/**
|
||||
* Maximum number of stories to fetch
|
||||
*
|
||||
* @default Infinity
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Whether to fetch stories from "archived" (or "hidden") peers
|
||||
*/
|
||||
archived?: boolean
|
||||
},
|
||||
): AsyncIterableIterator<PeerStories> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { archived, limit = Infinity } = params
|
||||
let { offset } = params
|
||||
let current = 0
|
||||
|
||||
for (;;) {
|
||||
const res = await this.getAllStories({
|
||||
offset,
|
||||
archived,
|
||||
})
|
||||
|
||||
for (const peer of res.peerStories) {
|
||||
yield peer
|
||||
|
||||
if (++current >= limit) return
|
||||
}
|
||||
|
||||
if (!res.hasMore) return
|
||||
offset = res.next
|
||||
}
|
||||
}
|
56
packages/client/src/methods/stories/iter-boosters.ts
Normal file
56
packages/client/src/methods/stories/iter-boosters.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
import { Booster } from '../../types/stories/booster'
|
||||
|
||||
/**
|
||||
* Iterate over boosters of a channel.
|
||||
*
|
||||
* Wrapper over {@link getBoosters}
|
||||
*
|
||||
* @returns IDs of stories that were removed
|
||||
* @internal
|
||||
*/
|
||||
export async function* iterBoosters(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getBoosters']>[1] & {
|
||||
/**
|
||||
* Total number of boosters to fetch
|
||||
*
|
||||
* @default Infinity, i.e. fetch all boosters
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of boosters to fetch per request
|
||||
* Usually you don't need to change this
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<Booster> {
|
||||
if (!params) params = {}
|
||||
const { limit = Infinity, chunkSize = 100 } = params
|
||||
|
||||
let { offset } = params
|
||||
let current = 0
|
||||
|
||||
const peer = await this.resolvePeer(peerId)
|
||||
|
||||
for (;;) {
|
||||
const res = await this.getBoosters(peer, {
|
||||
offset,
|
||||
limit: Math.min(limit - current, chunkSize),
|
||||
})
|
||||
|
||||
for (const booster of res) {
|
||||
yield booster
|
||||
|
||||
if (++current >= limit) return
|
||||
}
|
||||
|
||||
if (!res.next) return
|
||||
offset = res.next
|
||||
}
|
||||
}
|
54
packages/client/src/methods/stories/iter-profile-stories.ts
Normal file
54
packages/client/src/methods/stories/iter-profile-stories.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, Story } from '../../types'
|
||||
|
||||
/**
|
||||
* Iterate over profile stories. Wrapper over {@link getProfileStories}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function* iterProfileStories(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
params?: Parameters<TelegramClient['getProfileStories']>[1] & {
|
||||
/**
|
||||
* Total number of stories to fetch
|
||||
*
|
||||
* @default `Infinity`, i.e. fetch all stories
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of stories to fetch per request.
|
||||
* Usually you shouldn't care about this.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<Story> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { kind = 'pinned', limit = Infinity, chunkSize = 100 } = params
|
||||
|
||||
let { offsetId } = params
|
||||
let current = 0
|
||||
|
||||
const peer = await this.resolvePeer(peerId)
|
||||
|
||||
for (;;) {
|
||||
const res = await this.getProfileStories(peer, {
|
||||
kind,
|
||||
offsetId,
|
||||
limit: Math.min(limit - current, chunkSize),
|
||||
})
|
||||
|
||||
for (const peer of res) {
|
||||
yield peer
|
||||
|
||||
if (++current >= limit) return
|
||||
}
|
||||
|
||||
if (!res.next) return
|
||||
offsetId = res.next
|
||||
}
|
||||
}
|
58
packages/client/src/methods/stories/iter-story-viewers.ts
Normal file
58
packages/client/src/methods/stories/iter-story-viewers.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, StoryViewer } from '../../types'
|
||||
|
||||
/**
|
||||
* Iterate over viewers list of a story.
|
||||
* Wrapper over {@link getStoryViewers}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function* iterStoryViewers(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
params?: Parameters<TelegramClient['getStoryViewers']>[2] & {
|
||||
/**
|
||||
* Total number of viewers to fetch
|
||||
*
|
||||
* @default Infinity, i.e. fetch all viewers
|
||||
*/
|
||||
limit?: number
|
||||
|
||||
/**
|
||||
* Number of viewers to fetch per request.
|
||||
* Usually you don't need to change this.
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
chunkSize?: number
|
||||
},
|
||||
): AsyncIterableIterator<StoryViewer> {
|
||||
if (!params) params = {}
|
||||
|
||||
const { onlyContacts, sortBy = 'reaction', query, limit = Infinity, chunkSize = 100 } = params
|
||||
|
||||
let { offset = '' } = params
|
||||
let current = 0
|
||||
|
||||
const peer = await this.resolvePeer(peerId)
|
||||
|
||||
for (;;) {
|
||||
const res = await this.getStoryViewers(peer, storyId, {
|
||||
onlyContacts,
|
||||
sortBy,
|
||||
query,
|
||||
offset,
|
||||
limit: Math.min(limit - current, chunkSize),
|
||||
})
|
||||
|
||||
for (const peer of res.viewers) {
|
||||
yield peer
|
||||
|
||||
if (++current >= limit) return
|
||||
}
|
||||
|
||||
if (!res.next) return
|
||||
offset = res.next
|
||||
}
|
||||
}
|
19
packages/client/src/methods/stories/read-stories.ts
Normal file
19
packages/client/src/methods/stories/read-stories.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Mark all stories up to a given ID as read
|
||||
*
|
||||
* This should only be used for "active" stories ({@link Story#isActive} == false)
|
||||
*
|
||||
* @param peerId Peer ID whose stories to mark as read
|
||||
* @returns IDs of the stores that were marked as read
|
||||
* @internal
|
||||
*/
|
||||
export async function readStories(this: TelegramClient, peerId: InputPeerLike, maxId: number): Promise<number[]> {
|
||||
return this.call({
|
||||
_: 'stories.readStories',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
maxId,
|
||||
})
|
||||
}
|
38
packages/client/src/methods/stories/report-story.ts
Normal file
38
packages/client/src/methods/stories/report-story.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { MaybeArray, tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Report a story (or multiple stories) to the moderation team
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function reportStory(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyIds: MaybeArray<number>,
|
||||
params?: {
|
||||
/**
|
||||
* Reason for reporting
|
||||
*
|
||||
* @default inputReportReasonSpam
|
||||
*/
|
||||
reason?: tl.TypeReportReason
|
||||
|
||||
/**
|
||||
* Additional comment to the report
|
||||
*/
|
||||
message?: string
|
||||
},
|
||||
): Promise<void> {
|
||||
const { reason = { _: 'inputReportReasonSpam' }, message = '' } = params ?? {}
|
||||
|
||||
await this.call({
|
||||
_: 'stories.report',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
id: Array.isArray(storyIds) ? storyIds : [storyIds],
|
||||
message,
|
||||
reason,
|
||||
})
|
||||
}
|
32
packages/client/src/methods/stories/send-story-reaction.ts
Normal file
32
packages/client/src/methods/stories/send-story-reaction.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike, InputReaction, normalizeInputReaction } from '../../types'
|
||||
|
||||
/**
|
||||
* Send (or remove) a reaction to a story
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function sendStoryReaction(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
storyId: number,
|
||||
reaction: InputReaction,
|
||||
params?: {
|
||||
/**
|
||||
* Whether to add this reaction to recently used
|
||||
*/
|
||||
addToRecent?: boolean
|
||||
},
|
||||
): Promise<void> {
|
||||
const { addToRecent } = params ?? {}
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.sendReaction',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
storyId,
|
||||
reaction: normalizeInputReaction(reaction),
|
||||
addToRecent,
|
||||
})
|
||||
|
||||
this._handleUpdate(res, true)
|
||||
}
|
118
packages/client/src/methods/stories/send-story.ts
Normal file
118
packages/client/src/methods/stories/send-story.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
import { randomLong } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { FormattedString, InputMediaLike, InputPeerLike, InputPrivacyRule, Story } from '../../types'
|
||||
|
||||
/**
|
||||
* Send a story
|
||||
*
|
||||
* @returns Created story
|
||||
* @internal
|
||||
*/
|
||||
export async function sendStory(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Peer ID to send story as
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
|
||||
/**
|
||||
* Media contained in a story. Currently can only be a photo or a video.
|
||||
*
|
||||
* You can also pass TDLib and Bot API compatible File ID,
|
||||
* which will be wrapped in {@link InputMedia.auto}
|
||||
*/
|
||||
media: InputMediaLike | string
|
||||
|
||||
/**
|
||||
* Override caption for {@link media}
|
||||
*/
|
||||
caption?: string | FormattedString<string>
|
||||
|
||||
/**
|
||||
* Override entities for {@link media}
|
||||
*/
|
||||
entities?: tl.TypeMessageEntity[]
|
||||
|
||||
/**
|
||||
* Parse mode to use to parse entities before sending
|
||||
* the message. Defaults to current default parse mode (if any).
|
||||
*
|
||||
* Passing `null` will explicitly disable formatting.
|
||||
*/
|
||||
parseMode?: string | null
|
||||
|
||||
/**
|
||||
* Whether to automatically pin this story to the profile
|
||||
*/
|
||||
pinned?: boolean
|
||||
|
||||
/**
|
||||
* Whether to disallow sharing this story
|
||||
*/
|
||||
forbidForwards?: boolean
|
||||
|
||||
/**
|
||||
* Interactive elements to add to the story
|
||||
*/
|
||||
interactiveElements?: tl.TypeMediaArea[]
|
||||
|
||||
/**
|
||||
* Privacy rules to apply to the story
|
||||
*
|
||||
* @default "Everyone"
|
||||
*/
|
||||
privacyRules?: InputPrivacyRule[]
|
||||
|
||||
/**
|
||||
* TTL period of the story, in seconds
|
||||
*
|
||||
* @default 86400
|
||||
*/
|
||||
period?: number
|
||||
},
|
||||
): Promise<Story> {
|
||||
const { peer = 'me', pinned, forbidForwards, interactiveElements, period } = params
|
||||
let { media } = params
|
||||
|
||||
if (typeof media === 'string') {
|
||||
media = {
|
||||
type: 'auto',
|
||||
file: media,
|
||||
}
|
||||
}
|
||||
|
||||
const inputMedia = await this._normalizeInputMedia(media, params)
|
||||
const privacyRules = params.privacyRules ?
|
||||
await this._normalizePrivacyRules(params.privacyRules) :
|
||||
[{ _: 'inputPrivacyValueAllowAll' } as const]
|
||||
|
||||
const [caption, entities] = await this._parseEntities(
|
||||
// some types dont have `caption` field, and ts warns us,
|
||||
// but since it's JS, they'll just be `undefined` and properly
|
||||
// handled by _parseEntities method
|
||||
params.caption || (media as Extract<typeof media, { caption?: unknown }>).caption,
|
||||
params.parseMode,
|
||||
params.entities || (media as Extract<typeof media, { entities?: unknown }>).entities,
|
||||
)
|
||||
|
||||
const res = await this.call({
|
||||
_: 'stories.sendStory',
|
||||
pinned,
|
||||
noforwards: forbidForwards,
|
||||
peer: await this.resolvePeer(peer),
|
||||
media: inputMedia,
|
||||
mediaAreas: interactiveElements,
|
||||
caption,
|
||||
entities,
|
||||
privacyRules,
|
||||
randomId: randomLong(),
|
||||
period,
|
||||
})
|
||||
|
||||
return this._findStoryInUpdate(res)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Toggle whether peer's stories are archived (hidden) or not.
|
||||
*
|
||||
* This **does not** archive the chat with that peer, only stories.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function togglePeerStoriesArchived(
|
||||
this: TelegramClient,
|
||||
peerId: InputPeerLike,
|
||||
archived: boolean,
|
||||
): Promise<void> {
|
||||
await this.call({
|
||||
_: 'stories.togglePeerStoriesHidden',
|
||||
peer: await this.resolvePeer(peerId),
|
||||
hidden: archived,
|
||||
})
|
||||
}
|
41
packages/client/src/methods/stories/toggle-stories-pinned.ts
Normal file
41
packages/client/src/methods/stories/toggle-stories-pinned.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { MaybeArray } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { InputPeerLike } from '../../types'
|
||||
|
||||
/**
|
||||
* Toggle one or more stories pinned status
|
||||
*
|
||||
* @returns IDs of stories that were toggled
|
||||
* @internal
|
||||
*/
|
||||
export async function toggleStoriesPinned(
|
||||
this: TelegramClient,
|
||||
params: {
|
||||
/**
|
||||
* Story ID(s) to toggle
|
||||
*/
|
||||
ids: MaybeArray<number>
|
||||
|
||||
/**
|
||||
* Whether to pin or unpin the story
|
||||
*/
|
||||
pinned: boolean
|
||||
|
||||
/**
|
||||
* Peer ID whose stories to toggle
|
||||
*
|
||||
* @default `self`
|
||||
*/
|
||||
peer?: InputPeerLike
|
||||
},
|
||||
): Promise<number[]> {
|
||||
const { ids, pinned, peer = 'me' } = params
|
||||
|
||||
return await this.call({
|
||||
_: 'stories.togglePinned',
|
||||
peer: await this.resolvePeer(peer),
|
||||
id: Array.isArray(ids) ? ids : [ids],
|
||||
pinned,
|
||||
})
|
||||
}
|
|
@ -9,5 +9,7 @@ export * from './messages'
|
|||
export * from './misc'
|
||||
export * from './parser'
|
||||
export * from './peers'
|
||||
export * from './reactions'
|
||||
export * from './stories'
|
||||
export * from './updates'
|
||||
export * from './utils'
|
||||
|
|
|
@ -4,5 +4,5 @@ export * from './message'
|
|||
export * from './message-action'
|
||||
export * from './message-entity'
|
||||
export * from './message-media'
|
||||
export * from './reactions'
|
||||
export * from './message-reactions'
|
||||
export * from './search-filters'
|
||||
|
|
55
packages/client/src/types/messages/message-reactions.ts
Normal file
55
packages/client/src/types/messages/message-reactions.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../..'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex } from '../peers'
|
||||
import { PeerReaction } from '../reactions/peer-reaction'
|
||||
import { ReactionCount } from '../reactions/reaction-count'
|
||||
|
||||
/**
|
||||
* Reactions on a message
|
||||
*/
|
||||
export class MessageReactions {
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
readonly messageId: number,
|
||||
readonly chatId: number,
|
||||
readonly raw: tl.RawMessageReactions,
|
||||
readonly _peers: PeersIndex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Whether you can use {@link getUsers}
|
||||
* (or {@link TelegramClient.getReactionUsers})
|
||||
* to get the users who reacted to this message
|
||||
*/
|
||||
get usersVisible(): boolean {
|
||||
return this.raw.canSeeList!
|
||||
}
|
||||
|
||||
private _reactions?: ReactionCount[]
|
||||
/**
|
||||
* Reactions on the message, along with their counts
|
||||
*/
|
||||
get reactions(): ReactionCount[] {
|
||||
return (this._reactions ??= this.raw.results.map((it) => new ReactionCount(it)))
|
||||
}
|
||||
|
||||
private _recentReactions?: PeerReaction[]
|
||||
|
||||
/**
|
||||
* Recently reacted users.
|
||||
* To get a full list of users, use {@link getUsers}
|
||||
*/
|
||||
get recentReactions(): PeerReaction[] {
|
||||
if (!this.raw.recentReactions) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (this._recentReactions ??= this.raw.recentReactions.map(
|
||||
(reaction) => new PeerReaction(this.client, reaction, this._peers),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(MessageReactions)
|
|
@ -17,7 +17,7 @@ import { Chat, InputPeerLike, PeersIndex, User } from '../peers'
|
|||
import { _messageActionFromTl, MessageAction } from './message-action'
|
||||
import { MessageEntity } from './message-entity'
|
||||
import { _messageMediaFromTl, MessageMedia } from './message-media'
|
||||
import { MessageReactions } from './reactions'
|
||||
import { MessageReactions } from './message-reactions'
|
||||
|
||||
/** Information about a forward */
|
||||
export interface MessageForwardInfo {
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
import Long from 'long'
|
||||
|
||||
import { getMarkedPeerId, tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex, User } from '../peers'
|
||||
|
||||
/**
|
||||
* Emoji describing a reaction.
|
||||
*
|
||||
* Either a `string` with a unicode emoji, or a `tl.Long` for a custom emoji
|
||||
*/
|
||||
export type InputReaction = string | tl.Long | tl.TypeReaction
|
||||
|
||||
export function normalizeInputReaction(reaction?: InputReaction | null): tl.TypeReaction {
|
||||
if (typeof reaction === 'string') {
|
||||
return {
|
||||
_: 'reactionEmoji',
|
||||
emoticon: reaction,
|
||||
}
|
||||
} else if (Long.isLong(reaction)) {
|
||||
return {
|
||||
_: 'reactionCustomEmoji',
|
||||
documentId: reaction,
|
||||
}
|
||||
} else if (reaction) {
|
||||
return reaction
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'reactionEmpty',
|
||||
}
|
||||
}
|
||||
|
||||
export class PeerReaction {
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
readonly raw: tl.RawMessagePeerReaction,
|
||||
readonly _peers: PeersIndex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Emoji representing the reaction
|
||||
*/
|
||||
get emoji(): string {
|
||||
const r = this.raw.reaction
|
||||
|
||||
switch (r._) {
|
||||
case 'reactionCustomEmoji':
|
||||
return r.documentId.toString()
|
||||
case 'reactionEmoji':
|
||||
return r.emoticon
|
||||
case 'reactionEmpty':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this reaction is a custom emoji
|
||||
*/
|
||||
get isCustomEmoji(): boolean {
|
||||
return this.raw.reaction._ === 'reactionCustomEmoji'
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a big reaction
|
||||
*/
|
||||
get big(): boolean {
|
||||
return this.raw.big!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this reaction is unread by the current user
|
||||
*/
|
||||
get unread(): boolean {
|
||||
return this.raw.unread!
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the user who has reacted
|
||||
*/
|
||||
get userId(): number {
|
||||
return getMarkedPeerId(this.raw.peerId)
|
||||
}
|
||||
|
||||
private _user?: User
|
||||
|
||||
/**
|
||||
* User who has reacted
|
||||
*/
|
||||
get user(): User {
|
||||
if (!this._user) {
|
||||
assertTypeIs('PeerReaction#user', this.raw.peerId, 'peerUser')
|
||||
|
||||
this._user = new User(this.client, this._peers.user(this.raw.peerId.userId))
|
||||
}
|
||||
|
||||
return this._user
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(PeerReaction)
|
||||
|
||||
export class MessageReactions {
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
readonly messageId: number,
|
||||
readonly chatId: number,
|
||||
readonly raw: tl.RawMessageReactions,
|
||||
readonly _peers: PeersIndex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Whether you can use {@link getUsers}
|
||||
* (or {@link TelegramClient.getReactionUsers})
|
||||
* to get the users who reacted to this message
|
||||
*/
|
||||
get usersVisible(): boolean {
|
||||
return this.raw.canSeeList!
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactions on the message, along with their counts
|
||||
*/
|
||||
get reactions(): tl.TypeReactionCount[] {
|
||||
return this.raw.results
|
||||
}
|
||||
|
||||
private _recentReactions?: PeerReaction[]
|
||||
|
||||
/**
|
||||
* Recently reacted users.
|
||||
* To get a full list of users, use {@link getUsers}
|
||||
*/
|
||||
get recentReactions(): PeerReaction[] {
|
||||
if (!this.raw.recentReactions) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (this._recentReactions ??= this.raw.recentReactions.map(
|
||||
(reaction) => new PeerReaction(this.client, reaction, this._peers),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(MessageReactions)
|
|
@ -1,2 +1,3 @@
|
|||
export * from './input-privacy-rule'
|
||||
export * from './sticker-set'
|
||||
export * from './takeout-session'
|
||||
|
|
96
packages/client/src/types/misc/input-privacy-rule.ts
Normal file
96
packages/client/src/types/misc/input-privacy-rule.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import { MaybeArray, tl } from '@mtcute/core'
|
||||
|
||||
import { InputPeerLike } from '../peers'
|
||||
|
||||
interface InputPrivacyRuleUsers {
|
||||
allow: boolean
|
||||
users: InputPeerLike[]
|
||||
}
|
||||
|
||||
interface InputPrivacyRuleChatParticipants {
|
||||
allow: boolean
|
||||
chats: InputPeerLike[]
|
||||
}
|
||||
|
||||
export type InputPrivacyRule = InputPrivacyRuleChatParticipants | InputPrivacyRuleUsers | tl.TypeInputPrivacyRule
|
||||
|
||||
/**
|
||||
* Helpers for creating {@link InputPrivacyRule}s
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const rules = [
|
||||
* PrivacyRule.allow.all,
|
||||
* PrivacyRule.disallow.users([123456789, 'username']),
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
export namespace PrivacyRule {
|
||||
export namespace allow {
|
||||
/** Allow all users */
|
||||
export const all: tl.RawInputPrivacyValueAllowAll = { _: 'inputPrivacyValueAllowAll' }
|
||||
/** Allow only contacts */
|
||||
export const contacts: tl.RawInputPrivacyValueAllowContacts = { _: 'inputPrivacyValueAllowContacts' }
|
||||
/** Allow only "close friends" list */
|
||||
export const closeFriends: tl.RawInputPrivacyValueAllowCloseFriends = {
|
||||
_: 'inputPrivacyValueAllowCloseFriends',
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow only users specified in `users`
|
||||
*
|
||||
* @param users Users to allow
|
||||
*/
|
||||
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
|
||||
return {
|
||||
allow: true,
|
||||
users: Array.isArray(users) ? users : [users],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow only participants of chats specified in `chats`
|
||||
*
|
||||
* @param chats Chats to allow
|
||||
*/
|
||||
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
|
||||
return {
|
||||
allow: true,
|
||||
chats: Array.isArray(chats) ? chats : [chats],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace disallow {
|
||||
/** Disallow all users */
|
||||
export const all: tl.RawInputPrivacyValueDisallowAll = { _: 'inputPrivacyValueDisallowAll' }
|
||||
/** Disallow contacts */
|
||||
export const contacts: tl.RawInputPrivacyValueDisallowContacts = { _: 'inputPrivacyValueDisallowContacts' }
|
||||
|
||||
/**
|
||||
* Disallow users specified in `users`
|
||||
*
|
||||
* @param users Users to disallow
|
||||
*/
|
||||
export function users(users: MaybeArray<InputPeerLike>): InputPrivacyRuleUsers {
|
||||
return {
|
||||
allow: false,
|
||||
users: Array.isArray(users) ? users : [users],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow participants of chats specified in `chats`
|
||||
*
|
||||
* @param chats Chats to disallow
|
||||
*/
|
||||
export function chatParticipants(chats: MaybeArray<InputPeerLike>): InputPrivacyRuleChatParticipants {
|
||||
return {
|
||||
allow: false,
|
||||
chats: Array.isArray(chats) ? chats : [chats],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
packages/client/src/types/reactions/index.ts
Normal file
2
packages/client/src/types/reactions/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './peer-reaction'
|
||||
export * from './types'
|
63
packages/client/src/types/reactions/peer-reaction.ts
Normal file
63
packages/client/src/types/reactions/peer-reaction.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { getMarkedPeerId, tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../..'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex, User } from '../peers'
|
||||
import { ReactionEmoji, toReactionEmoji } from './types'
|
||||
|
||||
/**
|
||||
* Reactions of a user to a message
|
||||
*/
|
||||
export class PeerReaction {
|
||||
constructor(
|
||||
readonly client: TelegramClient,
|
||||
readonly raw: tl.RawMessagePeerReaction,
|
||||
readonly _peers: PeersIndex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Emoji representing the reaction
|
||||
*/
|
||||
get emoji(): ReactionEmoji {
|
||||
return toReactionEmoji(this.raw.reaction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a big reaction
|
||||
*/
|
||||
get big(): boolean {
|
||||
return this.raw.big!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this reaction is unread by the current user
|
||||
*/
|
||||
get unread(): boolean {
|
||||
return this.raw.unread!
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the user who has reacted
|
||||
*/
|
||||
get userId(): number {
|
||||
return getMarkedPeerId(this.raw.peerId)
|
||||
}
|
||||
|
||||
private _user?: User
|
||||
|
||||
/**
|
||||
* User who has reacted
|
||||
*/
|
||||
get user(): User {
|
||||
if (!this._user) {
|
||||
assertTypeIs('PeerReaction#user', this.raw.peerId, 'peerUser')
|
||||
|
||||
this._user = new User(this.client, this._peers.user(this.raw.peerId.userId))
|
||||
}
|
||||
|
||||
return this._user
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(PeerReaction)
|
36
packages/client/src/types/reactions/reaction-count.ts
Normal file
36
packages/client/src/types/reactions/reaction-count.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { ReactionEmoji, toReactionEmoji } from './types'
|
||||
|
||||
/**
|
||||
* Reaction count
|
||||
*/
|
||||
export class ReactionCount {
|
||||
constructor(readonly raw: tl.RawReactionCount) {}
|
||||
|
||||
/**
|
||||
* Emoji representing the reaction
|
||||
*/
|
||||
get emoji(): ReactionEmoji {
|
||||
return toReactionEmoji(this.raw.reaction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of users who reacted with this emoji
|
||||
*/
|
||||
get count(): number {
|
||||
return this.raw.count
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current user has reacted with this emoji,
|
||||
* this field will contain the order in which the
|
||||
* reaction was added.
|
||||
*/
|
||||
get order(): number | null {
|
||||
return this.raw.chosenOrder ?? null
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(ReactionCount)
|
53
packages/client/src/types/reactions/types.ts
Normal file
53
packages/client/src/types/reactions/types.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import Long from 'long'
|
||||
|
||||
import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
|
||||
/**
|
||||
* Input version of {@link ReactionEmoji}, which also accepts bare TL object
|
||||
*/
|
||||
export type InputReaction = string | tl.Long | tl.TypeReaction
|
||||
|
||||
/**
|
||||
* Emoji describing a reaction.
|
||||
*
|
||||
* Either a `string` with a unicode emoji, or a `tl.Long` for a custom emoji
|
||||
*/
|
||||
export type ReactionEmoji = string | tl.Long
|
||||
|
||||
export function normalizeInputReaction(reaction?: InputReaction | null): tl.TypeReaction {
|
||||
if (typeof reaction === 'string') {
|
||||
return {
|
||||
_: 'reactionEmoji',
|
||||
emoticon: reaction,
|
||||
}
|
||||
} else if (Long.isLong(reaction)) {
|
||||
return {
|
||||
_: 'reactionCustomEmoji',
|
||||
documentId: reaction,
|
||||
}
|
||||
} else if (reaction) {
|
||||
return reaction
|
||||
}
|
||||
|
||||
return {
|
||||
_: 'reactionEmpty',
|
||||
}
|
||||
}
|
||||
|
||||
export function toReactionEmoji(reaction: tl.TypeReaction, allowEmpty?: false): ReactionEmoji
|
||||
export function toReactionEmoji(reaction: tl.TypeReaction, allowEmpty: true): ReactionEmoji | null
|
||||
|
||||
export function toReactionEmoji(reaction: tl.TypeReaction, allowEmpty?: boolean): ReactionEmoji | null {
|
||||
switch (reaction._) {
|
||||
case 'reactionEmoji':
|
||||
return reaction.emoticon
|
||||
case 'reactionCustomEmoji':
|
||||
return reaction.documentId
|
||||
case 'reactionEmpty':
|
||||
if (!allowEmpty) {
|
||||
throw new MtTypeAssertionError('toReactionEmoji', 'not reactionEmpty', reaction._)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
48
packages/client/src/types/stories/all-stories.ts
Normal file
48
packages/client/src/types/stories/all-stories.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { PeersIndex, TelegramClient, tl } from '../..'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeerStories } from './peer-stories'
|
||||
import { StoriesStealthMode } from './stealth-mode'
|
||||
|
||||
/**
|
||||
* All stories of the current user
|
||||
*
|
||||
* Returned by {@link TelegramClient.getAllStories}
|
||||
*/
|
||||
export class AllStories {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.stories.RawAllStories) {}
|
||||
|
||||
readonly _peers = PeersIndex.from(this.raw)
|
||||
|
||||
/** Whether there are more stories to fetch */
|
||||
get hasMore(): boolean {
|
||||
return this.raw.hasMore!
|
||||
}
|
||||
|
||||
/** Next offset for pagination */
|
||||
get next(): string {
|
||||
return this.raw.state
|
||||
}
|
||||
|
||||
/** Total number of {@link PeerStories} available */
|
||||
get total(): number {
|
||||
return this.raw.count
|
||||
}
|
||||
|
||||
private _peerStories?: PeerStories[]
|
||||
/** Peers with their stories */
|
||||
get peerStories(): PeerStories[] {
|
||||
if (!this._peerStories) {
|
||||
this._peerStories = this.raw.peerStories.map((it) => new PeerStories(this.client, it, this._peers))
|
||||
}
|
||||
|
||||
return this._peerStories
|
||||
}
|
||||
|
||||
private _stealthMode?: StoriesStealthMode
|
||||
/** Stealth mode info */
|
||||
get stealthMode(): StoriesStealthMode | null {
|
||||
return (this._stealthMode ??= new StoriesStealthMode(this.raw.stealthMode))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(AllStories)
|
85
packages/client/src/types/stories/boost-stats.ts
Normal file
85
packages/client/src/types/stories/boost-stats.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { makeInspectable } from '../../utils'
|
||||
|
||||
/**
|
||||
* Information about boosts in a channel
|
||||
*/
|
||||
export class BoostStats {
|
||||
constructor(readonly raw: tl.stories.RawBoostsStatus) {}
|
||||
|
||||
/** Whether this channel is being boosted by the current user */
|
||||
get isBoosting(): boolean {
|
||||
return this.raw.myBoost!
|
||||
}
|
||||
|
||||
/**
|
||||
* Current level of boosts in this channel.
|
||||
*
|
||||
* Currently this maps 1-to-1 to the number of stories
|
||||
* the channel can post daily
|
||||
*/
|
||||
get level(): number {
|
||||
return this.raw.level
|
||||
}
|
||||
|
||||
/** Whether this channel is already at the maximum level */
|
||||
get isMaxLevel(): boolean {
|
||||
return this.raw.nextLevelBoosts === undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts that were needed for the current level
|
||||
*/
|
||||
get currentLevelBoosts(): number {
|
||||
return this.raw.currentLevelBoosts
|
||||
}
|
||||
|
||||
/** Total number of boosts this channel has */
|
||||
get currentBoosts(): number {
|
||||
return this.raw.boosts
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts the channel must have to reach the next level
|
||||
*
|
||||
* `null` if the channel is already at the maximum level
|
||||
*/
|
||||
get nextLevelBoosts(): number | null {
|
||||
return this.raw.nextLevelBoosts ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of boosts the channel needs in addition to the current value
|
||||
* to reach the next level
|
||||
*/
|
||||
get remainingBoosts(): number {
|
||||
if (!this.raw.nextLevelBoosts) return 0
|
||||
|
||||
return this.raw.nextLevelBoosts - this.raw.boosts
|
||||
}
|
||||
|
||||
/** If available, total number of subscribers this channel has */
|
||||
get totalSubscribers(): number | null {
|
||||
return this.raw.premiumAudience?.total ?? null
|
||||
}
|
||||
|
||||
/** If available, total number of Premium subscribers this channel has */
|
||||
get totalPremiumSubscribers(): number | null {
|
||||
return this.raw.premiumAudience?.part ?? null
|
||||
}
|
||||
|
||||
/** If available, percentage of this channel's subscribers that are Premium */
|
||||
get premiumSubscribersPercentage(): number | null {
|
||||
if (!this.raw.premiumAudience) return null
|
||||
|
||||
return (this.raw.premiumAudience.part / this.raw.premiumAudience.total) * 100
|
||||
}
|
||||
|
||||
/** URL that would bring up the boost interface */
|
||||
get url(): string {
|
||||
return this.raw.boostUrl
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(BoostStats)
|
31
packages/client/src/types/stories/booster.ts
Normal file
31
packages/client/src/types/stories/booster.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../..'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex, User } from '../peers'
|
||||
|
||||
/**
|
||||
* Information about a user who is boosting a channel
|
||||
*/
|
||||
export class Booster {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawBooster, readonly _peers: PeersIndex) {}
|
||||
|
||||
/**
|
||||
* Date when this boost will automatically expire.
|
||||
*
|
||||
* > **Note**: User can still manually cancel the boost before that date
|
||||
*/
|
||||
get expireDate(): Date {
|
||||
return new Date(this.raw.expires * 1000)
|
||||
}
|
||||
|
||||
private _user?: User
|
||||
/**
|
||||
* User who is boosting the channel
|
||||
*/
|
||||
get user(): User {
|
||||
return (this._user ??= new User(this.client, this._peers.user(this.raw.userId)))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(Booster)
|
9
packages/client/src/types/stories/index.ts
Normal file
9
packages/client/src/types/stories/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export * from './all-stories'
|
||||
export * from './boost-stats'
|
||||
export * from './booster'
|
||||
export * from './interactive'
|
||||
export * from './peer-stories'
|
||||
export * from './stealth-mode'
|
||||
export * from './story'
|
||||
export * from './story-interactions'
|
||||
export * from './story-viewer'
|
36
packages/client/src/types/stories/interactive/base.ts
Normal file
36
packages/client/src/types/stories/interactive/base.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { tl } from '@mtcute/core/src'
|
||||
|
||||
import { TelegramClient } from '../../../client'
|
||||
|
||||
export abstract class StoryInteractiveArea {
|
||||
abstract type: string
|
||||
|
||||
constructor(readonly client: TelegramClient, readonly raw: Exclude<tl.TypeMediaArea, tl.RawInputMediaAreaVenue>) {
|
||||
this.raw = raw
|
||||
}
|
||||
|
||||
/** X coordinate of the top-left corner of the area */
|
||||
get x(): number {
|
||||
return this.raw.coordinates.x
|
||||
}
|
||||
|
||||
/** Y coordinate of the top-left corner of the area */
|
||||
get y(): number {
|
||||
return this.raw.coordinates.y
|
||||
}
|
||||
|
||||
/** Width of the area */
|
||||
get width(): number {
|
||||
return this.raw.coordinates.w
|
||||
}
|
||||
|
||||
/** Height of the area */
|
||||
get height(): number {
|
||||
return this.raw.coordinates.h
|
||||
}
|
||||
|
||||
/** Rotation of the area */
|
||||
get rotation(): number {
|
||||
return this.raw.coordinates.rotation
|
||||
}
|
||||
}
|
23
packages/client/src/types/stories/interactive/index.ts
Normal file
23
packages/client/src/types/stories/interactive/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { MtTypeAssertionError, tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../../client'
|
||||
import { StoryInteractiveLocation } from './location'
|
||||
import { StoryInteractiveReaction } from './reaction'
|
||||
import { StoryInteractiveVenue } from './venue'
|
||||
|
||||
export * from './input'
|
||||
|
||||
export type StoryInteractiveElement = StoryInteractiveReaction | StoryInteractiveLocation | StoryInteractiveVenue
|
||||
|
||||
export function _storyInteractiveElementFromTl(client: TelegramClient, raw: tl.TypeMediaArea): StoryInteractiveElement {
|
||||
switch (raw._) {
|
||||
case 'mediaAreaSuggestedReaction':
|
||||
return new StoryInteractiveReaction(client, raw)
|
||||
case 'mediaAreaGeoPoint':
|
||||
return new StoryInteractiveLocation(client, raw)
|
||||
case 'mediaAreaVenue':
|
||||
return new StoryInteractiveVenue(client, raw)
|
||||
case 'inputMediaAreaVenue':
|
||||
throw new MtTypeAssertionError('StoryInteractiveElement', '!input*', raw._)
|
||||
}
|
||||
}
|
133
packages/client/src/types/stories/interactive/input.ts
Normal file
133
packages/client/src/types/stories/interactive/input.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import Long from 'long'
|
||||
|
||||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { VenueSource } from '../../media'
|
||||
import { InputReaction, normalizeInputReaction } from '../../reactions'
|
||||
|
||||
/**
|
||||
* Constructor for interactive story elements.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const element = StoryElement
|
||||
* .at({ x: 0, y: 0, width: 10, height: 10 })
|
||||
* .reaction('👍', { dark: true })
|
||||
* ```
|
||||
*/
|
||||
export class StoryElement {
|
||||
private constructor(private _position: tl.RawMediaAreaCoordinates) {}
|
||||
|
||||
static at(params: { x: number; y: number; width: number; height: number; rotation?: number }) {
|
||||
return new StoryElement({
|
||||
_: 'mediaAreaCoordinates',
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
w: params.width,
|
||||
h: params.height,
|
||||
rotation: params.rotation ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
venue(params: {
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
*/
|
||||
latitude: number
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
*/
|
||||
longitude: number
|
||||
|
||||
/**
|
||||
* Venue name
|
||||
*/
|
||||
title: string
|
||||
|
||||
/**
|
||||
* Venue address
|
||||
*/
|
||||
address: string
|
||||
|
||||
/**
|
||||
* Source where this venue was acquired
|
||||
*/
|
||||
source: VenueSource
|
||||
}): tl.RawMediaAreaVenue {
|
||||
return {
|
||||
_: 'mediaAreaVenue',
|
||||
coordinates: this._position,
|
||||
geo: {
|
||||
_: 'geoPoint',
|
||||
lat: params.latitude,
|
||||
long: params.longitude,
|
||||
accessHash: Long.ZERO,
|
||||
},
|
||||
title: params.title,
|
||||
address: params.address,
|
||||
provider: params.source.provider ?? 'foursquare',
|
||||
venueId: params.source.id,
|
||||
venueType: params.source.type,
|
||||
}
|
||||
}
|
||||
|
||||
venueFromInline(queryId: tl.Long, resultId: string): tl.RawInputMediaAreaVenue {
|
||||
return {
|
||||
_: 'inputMediaAreaVenue',
|
||||
coordinates: this._position,
|
||||
queryId,
|
||||
resultId,
|
||||
}
|
||||
}
|
||||
|
||||
location(params: {
|
||||
/**
|
||||
* Latitude of the geolocation
|
||||
*/
|
||||
latitude: number
|
||||
|
||||
/**
|
||||
* Longitude of the geolocation
|
||||
*/
|
||||
longitude: number
|
||||
}): tl.RawMediaAreaGeoPoint {
|
||||
return {
|
||||
_: 'mediaAreaGeoPoint',
|
||||
coordinates: this._position,
|
||||
geo: {
|
||||
_: 'geoPoint',
|
||||
lat: params.latitude,
|
||||
long: params.longitude,
|
||||
accessHash: Long.ZERO,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
reaction(
|
||||
reaction: InputReaction,
|
||||
params: {
|
||||
/**
|
||||
* Whether this reaction is on a dark background
|
||||
*/
|
||||
dark?: boolean
|
||||
|
||||
/**
|
||||
* Whether this reaction is flipped (i.e. has tail on the left)
|
||||
*/
|
||||
flipped?: boolean
|
||||
} = {},
|
||||
): tl.RawMediaAreaSuggestedReaction {
|
||||
// for whatever reason, in MTProto dimensions of these are expected to be 16:9/
|
||||
// we adjust them here to make it easier to work with
|
||||
this._position.h *= 9 / 16
|
||||
|
||||
return {
|
||||
_: 'mediaAreaSuggestedReaction',
|
||||
coordinates: this._position,
|
||||
reaction: normalizeInputReaction(reaction),
|
||||
dark: params.dark,
|
||||
flipped: params.flipped,
|
||||
}
|
||||
}
|
||||
}
|
33
packages/client/src/types/stories/interactive/location.ts
Normal file
33
packages/client/src/types/stories/interactive/location.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../../client'
|
||||
import { makeInspectable } from '../../../utils'
|
||||
import { Location } from '../../media'
|
||||
import { StoryInteractiveArea } from './base'
|
||||
|
||||
/**
|
||||
* Interactive element containing a location on the map
|
||||
*/
|
||||
export class StoryInteractiveLocation extends StoryInteractiveArea {
|
||||
readonly type = 'location' as const
|
||||
|
||||
constructor(client: TelegramClient, readonly raw: tl.RawMediaAreaGeoPoint) {
|
||||
super(client, raw)
|
||||
}
|
||||
|
||||
private _location?: Location
|
||||
/**
|
||||
* Geolocation
|
||||
*/
|
||||
get location(): Location {
|
||||
if (!this._location) {
|
||||
assertTypeIs('StoryInteractiveLocation#location', this.raw.geo, 'geoPoint')
|
||||
this._location = new Location(this.client, this.raw.geo)
|
||||
}
|
||||
|
||||
return this._location
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryInteractiveLocation)
|
38
packages/client/src/types/stories/interactive/reaction.ts
Normal file
38
packages/client/src/types/stories/interactive/reaction.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../../client'
|
||||
import { makeInspectable } from '../../../utils'
|
||||
import { ReactionEmoji, toReactionEmoji } from '../../reactions'
|
||||
import { StoryInteractiveArea } from './base'
|
||||
|
||||
/**
|
||||
* Interactive element containing a reaction.
|
||||
*
|
||||
* Number of reactions should be taken from {@link StoryViews} by emoji ID
|
||||
*
|
||||
* For whatever reason, in MTProto dimensions of these are expected to be 16:9
|
||||
*/
|
||||
export class StoryInteractiveReaction extends StoryInteractiveArea {
|
||||
readonly type = 'reaction' as const
|
||||
|
||||
constructor(client: TelegramClient, readonly raw: tl.RawMediaAreaSuggestedReaction) {
|
||||
super(client, raw)
|
||||
}
|
||||
|
||||
/** Whether this reaction is on a dark background */
|
||||
get isDark(): boolean {
|
||||
return this.raw.dark!
|
||||
}
|
||||
|
||||
/** Whether this reaction is flipped (i.e. has tail on the left) */
|
||||
get isFlipped(): boolean {
|
||||
return this.raw.flipped!
|
||||
}
|
||||
|
||||
/** Emoji representing the reaction */
|
||||
get emoji(): ReactionEmoji {
|
||||
return toReactionEmoji(this.raw.reaction)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryInteractiveReaction)
|
60
packages/client/src/types/stories/interactive/venue.ts
Normal file
60
packages/client/src/types/stories/interactive/venue.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
import { assertTypeIs } from '@mtcute/core/utils'
|
||||
|
||||
import { TelegramClient } from '../../../client'
|
||||
import { makeInspectable } from '../../../utils'
|
||||
import { Location, VenueSource } from '../../media'
|
||||
import { StoryInteractiveArea } from './base'
|
||||
|
||||
/**
|
||||
* Interactive element containing a venue
|
||||
*/
|
||||
export class StoryInteractiveVenue extends StoryInteractiveArea {
|
||||
readonly type = 'venue' as const
|
||||
|
||||
constructor(client: TelegramClient, readonly raw: tl.RawMediaAreaVenue) {
|
||||
super(client, raw)
|
||||
}
|
||||
|
||||
private _location?: Location
|
||||
/**
|
||||
* Geolocation of the venue
|
||||
*/
|
||||
get location(): Location {
|
||||
if (!this._location) {
|
||||
assertTypeIs('StoryInteractiveVenue#location', this.raw.geo, 'geoPoint')
|
||||
this._location = new Location(this.client, this.raw.geo)
|
||||
}
|
||||
|
||||
return this._location
|
||||
}
|
||||
|
||||
/**
|
||||
* Venue name
|
||||
*/
|
||||
get title(): string {
|
||||
return this.raw.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Venue address
|
||||
*/
|
||||
get address(): string {
|
||||
return this.raw.address
|
||||
}
|
||||
|
||||
/**
|
||||
* When available, source from where this venue was acquired
|
||||
*/
|
||||
get source(): VenueSource | null {
|
||||
if (!this.raw.provider) return null
|
||||
|
||||
return {
|
||||
provider: this.raw.provider as VenueSource['provider'],
|
||||
id: this.raw.venueId,
|
||||
type: this.raw.venueType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryInteractiveVenue)
|
48
packages/client/src/types/stories/peer-stories.ts
Normal file
48
packages/client/src/types/stories/peer-stories.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { assertTypeIs, makeInspectable } from '../../utils'
|
||||
import { Chat, PeersIndex, User } from '../peers'
|
||||
import { Story } from './story'
|
||||
|
||||
export class PeerStories {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawPeerStories, readonly _peers: PeersIndex) {}
|
||||
|
||||
private _peer?: User | Chat
|
||||
/**
|
||||
* Peer that owns these stories.
|
||||
*/
|
||||
get peer(): User | Chat {
|
||||
if (this._peer) return this._peer
|
||||
|
||||
switch (this.raw.peer._) {
|
||||
case 'peerUser':
|
||||
return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId)))
|
||||
case 'peerChat':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId)))
|
||||
case 'peerChannel':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the last read story of this peer.
|
||||
*/
|
||||
get maxReadId(): number {
|
||||
return this.raw.maxReadId ?? 0
|
||||
}
|
||||
|
||||
private _stories?: Story[]
|
||||
/**
|
||||
* List of peer stories.
|
||||
*/
|
||||
get stories(): Story[] {
|
||||
return (this._stories ??= this.raw.stories.map((it) => {
|
||||
assertTypeIs('PeerStories#stories', it, 'storyItem')
|
||||
|
||||
return new Story(this.client, it, this._peers)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(PeerStories)
|
23
packages/client/src/types/stories/stealth-mode.ts
Normal file
23
packages/client/src/types/stories/stealth-mode.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { makeInspectable } from '../../utils'
|
||||
|
||||
export class StoriesStealthMode {
|
||||
constructor(readonly raw: tl.RawStoriesStealthMode) {}
|
||||
|
||||
/** Stealth mode is active until this date */
|
||||
get activeUntil(): Date | null {
|
||||
if (!this.raw.activeUntilDate) return null
|
||||
|
||||
return new Date(this.raw.activeUntilDate * 1000)
|
||||
}
|
||||
|
||||
/** Stealth mode is having a cooldown until this date */
|
||||
get cooldownUntil(): Date | null {
|
||||
if (!this.raw.cooldownUntilDate) return null
|
||||
|
||||
return new Date(this.raw.cooldownUntilDate * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoriesStealthMode)
|
63
packages/client/src/types/stories/story-interactions.ts
Normal file
63
packages/client/src/types/stories/story-interactions.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex, User } from '../peers'
|
||||
import { ReactionCount } from '../reactions/reaction-count'
|
||||
|
||||
/**
|
||||
* Brief information about story views/interactions
|
||||
*/
|
||||
export class StoryInteractions {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawStoryViews, readonly _peers: PeersIndex) {}
|
||||
|
||||
/**
|
||||
* Whether information about viewers is available.
|
||||
*
|
||||
* When `true`, you can use {@link TelegarmClient.getStoryViewers}
|
||||
* to get the full list of viewers, and also {@link recentViewers}
|
||||
* will be available.
|
||||
*/
|
||||
get hasViewers(): boolean {
|
||||
return this.raw.hasViewers!
|
||||
}
|
||||
|
||||
/** Number of views */
|
||||
get viewsCount(): number {
|
||||
return this.raw.viewsCount
|
||||
}
|
||||
|
||||
/** Number of forwards (if available) */
|
||||
get forwardsCount(): number | null {
|
||||
return this.raw.forwardsCount ?? null
|
||||
}
|
||||
|
||||
/** Total number of reactions */
|
||||
get reactionsCount(): number {
|
||||
return this.raw.reactionsCount ?? 0
|
||||
}
|
||||
|
||||
private _reactions?: ReactionCount[]
|
||||
/**
|
||||
* Reactions on the message, along with their counts
|
||||
*/
|
||||
get reactions(): ReactionCount[] {
|
||||
if (!this.raw.reactions) return []
|
||||
|
||||
return (this._reactions ??= this.raw.reactions.map((it) => new ReactionCount(it)))
|
||||
}
|
||||
|
||||
private _recentViewers?: User[]
|
||||
/**
|
||||
* List of users who have recently viewed this story.
|
||||
*/
|
||||
get recentViewers(): User[] {
|
||||
if (!this._recentViewers) {
|
||||
this._recentViewers = this.raw.recentViewers?.map((it) => new User(this.client, this._peers.user(it))) ?? []
|
||||
}
|
||||
|
||||
return this._recentViewers
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryInteractions)
|
79
packages/client/src/types/stories/story-viewer.ts
Normal file
79
packages/client/src/types/stories/story-viewer.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { PeersIndex, User } from '../peers'
|
||||
import { ReactionEmoji, toReactionEmoji } from '../reactions'
|
||||
|
||||
/**
|
||||
* Information about a single user who has viewed a story.
|
||||
*/
|
||||
export class StoryViewer {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawStoryView, readonly _peers: PeersIndex) {}
|
||||
|
||||
/** Whether this user is in current user's global blacklist */
|
||||
get isBlocked(): boolean {
|
||||
return this.raw.blocked!
|
||||
}
|
||||
|
||||
/** Whether current user's stories are hidden from this user */
|
||||
get isStoriesBlocked(): boolean {
|
||||
return this.raw.blockedMyStoriesFrom!
|
||||
}
|
||||
|
||||
/** Date when the view has occurred */
|
||||
get date(): Date {
|
||||
return new Date(this.raw.date * 1000)
|
||||
}
|
||||
|
||||
/** Reaction this user has left, if any */
|
||||
get reactionEmoji(): ReactionEmoji | null {
|
||||
if (!this.raw.reaction) return null
|
||||
|
||||
return toReactionEmoji(this.raw.reaction, true)
|
||||
}
|
||||
|
||||
private _user?: User
|
||||
/** Information about the user */
|
||||
get user(): User {
|
||||
return (this._user ??= new User(this.client, this._peers.user(this.raw.userId)))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryViewer)
|
||||
|
||||
/**
|
||||
* List of story viewers.
|
||||
*/
|
||||
export class StoryViewersList {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.stories.RawStoryViewsList) {}
|
||||
|
||||
readonly _peers = PeersIndex.from(this.raw)
|
||||
|
||||
/** Next offset for pagination */
|
||||
get next(): string | undefined {
|
||||
return this.raw.nextOffset
|
||||
}
|
||||
|
||||
/** Total number of views this story has */
|
||||
get total(): number {
|
||||
return this.raw.count
|
||||
}
|
||||
|
||||
/** Total number of reactions this story has */
|
||||
get reactionsTotal(): number {
|
||||
return this.raw.reactionsCount
|
||||
}
|
||||
|
||||
private _viewers?: StoryViewer[]
|
||||
/** List of viewers */
|
||||
get viewers(): StoryViewer[] {
|
||||
if (!this._viewers) {
|
||||
this._viewers = this.raw.views.map((it) => new StoryViewer(this.client, it, this._peers))
|
||||
}
|
||||
|
||||
return this._viewers
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryViewersList)
|
180
packages/client/src/types/stories/story.ts
Normal file
180
packages/client/src/types/stories/story.ts
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { MtUnsupportedError, tl } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../../utils'
|
||||
import { Photo, Video } from '../media'
|
||||
import { _messageMediaFromTl, MessageEntity } from '../messages'
|
||||
import { PeersIndex } from '../peers'
|
||||
import { ReactionEmoji, toReactionEmoji } from '../reactions'
|
||||
import { _storyInteractiveElementFromTl, StoryInteractiveElement } from './interactive'
|
||||
import { StoryInteractions } from './story-interactions'
|
||||
|
||||
/**
|
||||
* Information about story visibility.
|
||||
*
|
||||
* - `public` - story is visible to everyone
|
||||
* - `contacts` - story is visible only to contacts
|
||||
* - `selectedContacts` - story is visible only to some contacts
|
||||
* - `closeFriends` - story is visible only to "close friends"
|
||||
*/
|
||||
export type StoryVisibility = 'public' | 'contacts' | 'selected_contacts' | 'close_friends'
|
||||
|
||||
export type StoryMedia = Photo | Video
|
||||
|
||||
export class Story {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawStoryItem, readonly _peers: PeersIndex) {}
|
||||
|
||||
/** Whether this story is pinned */
|
||||
get isPinned(): boolean {
|
||||
return this.raw.pinned!
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this object contains reduced set of fields.
|
||||
*
|
||||
* When `true`, these field will not contain correct data:
|
||||
* {@link privacyRules}, {@link interactiveAreas}
|
||||
*
|
||||
*/
|
||||
get isShort(): boolean {
|
||||
return this.raw.min!
|
||||
}
|
||||
|
||||
/** Whether this story is content-protected, i.e. can't be forwarded */
|
||||
get isContentProtected(): boolean {
|
||||
return this.raw.noforwards!
|
||||
}
|
||||
|
||||
/** Whether this story has been edited */
|
||||
get isEdited(): boolean {
|
||||
return this.raw.edited!
|
||||
}
|
||||
|
||||
/** Whether this story has been posted by the current user */
|
||||
get isMy(): boolean {
|
||||
return this.raw.out!
|
||||
}
|
||||
|
||||
/** ID of the story */
|
||||
get id(): number {
|
||||
return this.raw.id
|
||||
}
|
||||
|
||||
/** Date when this story was posted */
|
||||
get date(): Date {
|
||||
return new Date(this.raw.date * 1000)
|
||||
}
|
||||
|
||||
/** Date when this story will expire */
|
||||
get expireDate(): Date {
|
||||
return new Date(this.raw.expireDate * 1000)
|
||||
}
|
||||
|
||||
/** Whether the story is active (i.e. not expired yet) */
|
||||
get isActive(): boolean {
|
||||
return Date.now() < this.expireDate.getTime()
|
||||
}
|
||||
|
||||
/** Story visibility */
|
||||
get visibility(): StoryVisibility {
|
||||
if (this.raw.public) return 'public'
|
||||
if (this.raw.contacts) return 'contacts'
|
||||
if (this.raw.closeFriends) return 'close_friends'
|
||||
if (this.raw.selectedContacts) return 'selected_contacts'
|
||||
|
||||
throw new MtUnsupportedError('Unknown story visibility')
|
||||
}
|
||||
|
||||
/** Caption of the story */
|
||||
get caption(): string | null {
|
||||
return this.raw.caption ?? null
|
||||
}
|
||||
|
||||
private _entities?: MessageEntity[]
|
||||
/**
|
||||
* Caption entities (may be empty)
|
||||
*/
|
||||
get entities(): ReadonlyArray<MessageEntity> {
|
||||
if (!this._entities) {
|
||||
this._entities = []
|
||||
|
||||
if (this.raw.entities?.length) {
|
||||
for (const ent of this.raw.entities) {
|
||||
const parsed = MessageEntity._parse(ent)
|
||||
if (parsed) this._entities.push(parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this._entities
|
||||
}
|
||||
|
||||
private _media?: StoryMedia
|
||||
/**
|
||||
* Story media.
|
||||
*
|
||||
* Currently, can only be {@link Photo} or {@link Video}.
|
||||
*/
|
||||
get media(): StoryMedia {
|
||||
if (this._media === undefined) {
|
||||
const media = _messageMediaFromTl(this.client, this._peers, this.raw.media)
|
||||
|
||||
switch (media?.type) {
|
||||
case 'photo':
|
||||
case 'video':
|
||||
this._media = media
|
||||
break
|
||||
default:
|
||||
throw new MtUnsupportedError('Unsupported story media type')
|
||||
}
|
||||
}
|
||||
|
||||
return this._media
|
||||
}
|
||||
|
||||
private _interactiveElements?: StoryInteractiveElement[]
|
||||
/**
|
||||
* Interactive elements of the story
|
||||
*/
|
||||
get interactiveElements() {
|
||||
if (!this.raw.mediaAreas) return []
|
||||
|
||||
if (this._interactiveElements === undefined) {
|
||||
this._interactiveElements = this.raw.mediaAreas.map((it) => _storyInteractiveElementFromTl(this.client, it))
|
||||
}
|
||||
|
||||
return this._interactiveElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Privacy rules of the story.
|
||||
*
|
||||
* Only available when {@link isMy} is `true`.
|
||||
*/
|
||||
get privacyRules(): tl.TypePrivacyRule[] | null {
|
||||
if (!this.raw.privacy) return null
|
||||
|
||||
return this.raw.privacy
|
||||
}
|
||||
|
||||
private _interactions?: StoryInteractions
|
||||
/**
|
||||
* Information about story interactions
|
||||
*/
|
||||
get interactions(): StoryInteractions | null {
|
||||
if (!this.raw.views) return null
|
||||
|
||||
return (this._interactions ??= new StoryInteractions(this.client, this.raw.views, this._peers))
|
||||
}
|
||||
|
||||
/**
|
||||
* Emoji representing a reaction sent by the current user, if any
|
||||
*/
|
||||
get sentReactionEmoji(): ReactionEmoji | null {
|
||||
if (!this.raw.sentReaction) return null
|
||||
|
||||
return toReactionEmoji(this.raw.sentReaction, true)
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(Story)
|
35
packages/client/src/types/updates/delete-story-update.ts
Normal file
35
packages/client/src/types/updates/delete-story-update.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { Chat, PeersIndex, TelegramClient, tl, User } from '../..'
|
||||
import { makeInspectable } from '../../utils'
|
||||
|
||||
/**
|
||||
* A story was deleted
|
||||
*/
|
||||
export class DeleteStoryUpdate {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawUpdateStory, readonly _peers: PeersIndex) {}
|
||||
|
||||
private _peer?: User | Chat
|
||||
/**
|
||||
* Peer that owns these stories.
|
||||
*/
|
||||
get peer(): User | Chat {
|
||||
if (this._peer) return this._peer
|
||||
|
||||
switch (this.raw.peer._) {
|
||||
case 'peerUser':
|
||||
return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId)))
|
||||
case 'peerChat':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId)))
|
||||
case 'peerChannel':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ID of the deleted story
|
||||
*/
|
||||
get storyId(): number {
|
||||
return this.raw.story.id
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(DeleteStoryUpdate)
|
|
@ -5,10 +5,12 @@ import { ChatJoinRequestUpdate } from './chat-join-request'
|
|||
import { ChatMemberUpdate, ChatMemberUpdateType } from './chat-member-update'
|
||||
import { ChosenInlineResult } from './chosen-inline-result'
|
||||
import { DeleteMessageUpdate } from './delete-message-update'
|
||||
import { DeleteStoryUpdate } from './delete-story-update'
|
||||
import { HistoryReadUpdate } from './history-read-update'
|
||||
import { PollUpdate } from './poll-update'
|
||||
import { PollVoteUpdate } from './poll-vote'
|
||||
import { PreCheckoutQuery } from './pre-checkout-query'
|
||||
import { StoryUpdate } from './story-update'
|
||||
import { UserStatusUpdate } from './user-status-update'
|
||||
import { UserTypingUpdate } from './user-typing-update'
|
||||
|
||||
|
@ -20,10 +22,12 @@ export {
|
|||
ChatMemberUpdateType,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
}
|
||||
|
@ -46,5 +50,7 @@ export type ParsedUpdate =
|
|||
| { name: 'bot_chat_join_request'; data: BotChatJoinRequestUpdate }
|
||||
| { name: 'chat_join_request'; data: ChatJoinRequestUpdate }
|
||||
| { name: 'pre_checkout_query'; data: PreCheckoutQuery }
|
||||
| { name: 'story'; data: StoryUpdate }
|
||||
| { name: 'delete_story'; data: DeleteStoryUpdate }
|
||||
|
||||
// end-codegen
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
Message,
|
||||
|
@ -18,103 +19,105 @@ import {
|
|||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
} from '../index'
|
||||
|
||||
type ParserFunction = (
|
||||
client: TelegramClient,
|
||||
upd: tl.TypeUpdate | tl.TypeMessage,
|
||||
peers: PeersIndex,
|
||||
) => ParsedUpdate['data']
|
||||
type UpdateParser = [ParsedUpdate['name'], ParserFunction]
|
||||
|
||||
const baseMessageParser: ParserFunction = (client: TelegramClient, upd, peers) =>
|
||||
new Message(
|
||||
client,
|
||||
tl.isAnyMessage(upd) ? upd : (upd as { message: tl.TypeMessage }).message,
|
||||
peers,
|
||||
upd._ === 'updateNewScheduledMessage',
|
||||
)
|
||||
|
||||
const newMessageParser: UpdateParser = ['new_message', baseMessageParser]
|
||||
const editMessageParser: UpdateParser = ['edit_message', baseMessageParser]
|
||||
const chatMemberParser: UpdateParser = [
|
||||
'chat_member',
|
||||
(client, upd, peers) => new ChatMemberUpdate(client, upd as any, peers),
|
||||
]
|
||||
const callbackQueryParser: UpdateParser = [
|
||||
'callback_query',
|
||||
(client, upd, peers) => new CallbackQuery(client, upd as any, peers),
|
||||
]
|
||||
const userTypingParser: UpdateParser = ['user_typing', (client, upd) => new UserTypingUpdate(client, upd as any)]
|
||||
const deleteMessageParser: UpdateParser = [
|
||||
'delete_message',
|
||||
(client, upd) => new DeleteMessageUpdate(client, upd as any),
|
||||
]
|
||||
const historyReadParser: UpdateParser = ['history_read', (client, upd) => new HistoryReadUpdate(client, upd as any)]
|
||||
|
||||
const PARSERS: Partial<Record<(tl.TypeUpdate | tl.TypeMessage)['_'], UpdateParser>> = {
|
||||
message: newMessageParser,
|
||||
messageEmpty: newMessageParser,
|
||||
messageService: newMessageParser,
|
||||
updateNewMessage: newMessageParser,
|
||||
updateNewChannelMessage: newMessageParser,
|
||||
updateNewScheduledMessage: newMessageParser,
|
||||
updateEditMessage: editMessageParser,
|
||||
updateEditChannelMessage: editMessageParser,
|
||||
updateChatParticipant: chatMemberParser,
|
||||
updateChannelParticipant: chatMemberParser,
|
||||
updateBotInlineQuery: ['inline_query', (client, upd, peers) => new InlineQuery(client, upd as any, peers)],
|
||||
updateBotInlineSend: [
|
||||
'chosen_inline_result',
|
||||
(client, upd, peers) => new ChosenInlineResult(client, upd as any, peers),
|
||||
],
|
||||
updateBotCallbackQuery: callbackQueryParser,
|
||||
updateInlineBotCallbackQuery: callbackQueryParser,
|
||||
updateMessagePoll: ['poll', (client, upd, peers) => new PollUpdate(client, upd as any, peers)],
|
||||
updateMessagePollVote: ['poll_vote', (client, upd, peers) => new PollVoteUpdate(client, upd as any, peers)],
|
||||
updateUserStatus: ['user_status', (client, upd) => new UserStatusUpdate(client, upd as any)],
|
||||
updateChannelUserTyping: userTypingParser,
|
||||
updateChatUserTyping: userTypingParser,
|
||||
updateUserTyping: userTypingParser,
|
||||
updateDeleteChannelMessages: deleteMessageParser,
|
||||
updateDeleteMessages: deleteMessageParser,
|
||||
updateReadHistoryInbox: historyReadParser,
|
||||
updateReadHistoryOutbox: historyReadParser,
|
||||
updateReadChannelInbox: historyReadParser,
|
||||
updateReadChannelOutbox: historyReadParser,
|
||||
updateReadChannelDiscussionInbox: historyReadParser,
|
||||
updateReadChannelDiscussionOutbox: historyReadParser,
|
||||
updateBotStopped: ['bot_stopped', (client, upd, peers) => new BotStoppedUpdate(client, upd as any, peers)],
|
||||
updateBotChatInviteRequester: [
|
||||
'bot_chat_join_request',
|
||||
(client, upd, peers) => new BotChatJoinRequestUpdate(client, upd as any, peers),
|
||||
],
|
||||
updatePendingJoinRequests: [
|
||||
'chat_join_request',
|
||||
(client, upd, peers) => new ChatJoinRequestUpdate(client, upd as any, peers),
|
||||
],
|
||||
updateBotPrecheckoutQuery: [
|
||||
'pre_checkout_query',
|
||||
(client, upd, peers) => new PreCheckoutQuery(client, upd as any, peers),
|
||||
],
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function _parseUpdate(
|
||||
client: TelegramClient,
|
||||
update: tl.TypeUpdate | tl.TypeMessage,
|
||||
peers: PeersIndex,
|
||||
): ParsedUpdate | null {
|
||||
const pair = PARSERS[update._]
|
||||
switch (update._) {
|
||||
case 'message':
|
||||
case 'messageEmpty':
|
||||
case 'messageService':
|
||||
case 'updateNewMessage':
|
||||
case 'updateNewChannelMessage':
|
||||
case 'updateNewScheduledMessage':
|
||||
return {
|
||||
name: 'new_message',
|
||||
data: new Message(
|
||||
client,
|
||||
tl.isAnyMessage(update) ? update : update.message,
|
||||
peers,
|
||||
update._ === 'updateNewScheduledMessage',
|
||||
),
|
||||
}
|
||||
break
|
||||
case 'updateEditMessage':
|
||||
case 'updateEditChannelMessage':
|
||||
return { name: 'edit_message', data: new Message(client, update.message, peers) }
|
||||
break
|
||||
case 'updateChatParticipant':
|
||||
case 'updateChannelParticipant':
|
||||
return { name: 'chat_member', data: new ChatMemberUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updateBotInlineQuery':
|
||||
return { name: 'inline_query', data: new InlineQuery(client, update, peers) }
|
||||
break
|
||||
case 'updateBotInlineSend':
|
||||
return { name: 'chosen_inline_result', data: new ChosenInlineResult(client, update, peers) }
|
||||
break
|
||||
case 'updateBotCallbackQuery':
|
||||
case 'updateInlineBotCallbackQuery':
|
||||
return { name: 'callback_query', data: new CallbackQuery(client, update, peers) }
|
||||
break
|
||||
case 'updateMessagePoll':
|
||||
return { name: 'poll', data: new PollUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updateMessagePollVote':
|
||||
return { name: 'poll_vote', data: new PollVoteUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updateUserStatus':
|
||||
return { name: 'user_status', data: new UserStatusUpdate(client, update) }
|
||||
break
|
||||
case 'updateChannelUserTyping':
|
||||
case 'updateChatUserTyping':
|
||||
case 'updateUserTyping':
|
||||
return { name: 'user_typing', data: new UserTypingUpdate(client, update) }
|
||||
break
|
||||
case 'updateDeleteChannelMessages':
|
||||
case 'updateDeleteMessages':
|
||||
return { name: 'delete_message', data: new DeleteMessageUpdate(client, update) }
|
||||
break
|
||||
case 'updateReadHistoryInbox':
|
||||
case 'updateReadHistoryOutbox':
|
||||
case 'updateReadChannelInbox':
|
||||
case 'updateReadChannelOutbox':
|
||||
case 'updateReadChannelDiscussionInbox':
|
||||
case 'updateReadChannelDiscussionOutbox':
|
||||
return { name: 'history_read', data: new HistoryReadUpdate(client, update) }
|
||||
break
|
||||
case 'updateBotStopped':
|
||||
return { name: 'bot_stopped', data: new BotStoppedUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updateBotChatInviteRequester':
|
||||
return { name: 'bot_chat_join_request', data: new BotChatJoinRequestUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updatePendingJoinRequests':
|
||||
return { name: 'chat_join_request', data: new ChatJoinRequestUpdate(client, update, peers) }
|
||||
break
|
||||
case 'updateBotPrecheckoutQuery':
|
||||
return { name: 'pre_checkout_query', data: new PreCheckoutQuery(client, update, peers) }
|
||||
break
|
||||
case 'updateStory': {
|
||||
const story = update.story
|
||||
|
||||
if (pair) {
|
||||
return {
|
||||
name: pair[0],
|
||||
data: pair[1](client, update, peers),
|
||||
} as ParsedUpdate
|
||||
if (story._ === 'storyItemDeleted') {
|
||||
return { name: 'delete_story', data: new DeleteStoryUpdate(client, update, peers) }
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'story',
|
||||
data: new StoryUpdate(client, update, peers),
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
43
packages/client/src/types/updates/story-update.ts
Normal file
43
packages/client/src/types/updates/story-update.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { Chat, PeersIndex, Story, TelegramClient, tl, User } from '../..'
|
||||
import { assertTypeIs, makeInspectable } from '../../utils'
|
||||
|
||||
/**
|
||||
* A story was posted or edited
|
||||
*
|
||||
* > **Note**: Currently the only way to reliably test if this is a new story or an update
|
||||
* > is to store known stories IDs and compare them to the one in the update.
|
||||
*/
|
||||
export class StoryUpdate {
|
||||
constructor(readonly client: TelegramClient, readonly raw: tl.RawUpdateStory, readonly _peers: PeersIndex) {}
|
||||
|
||||
private _peer?: User | Chat
|
||||
/**
|
||||
* Peer that owns these stories.
|
||||
*/
|
||||
get peer(): User | Chat {
|
||||
if (this._peer) return this._peer
|
||||
|
||||
switch (this.raw.peer._) {
|
||||
case 'peerUser':
|
||||
return (this._peer = new User(this.client, this._peers.user(this.raw.peer.userId)))
|
||||
case 'peerChat':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.chatId)))
|
||||
case 'peerChannel':
|
||||
return (this._peer = new Chat(this.client, this._peers.chat(this.raw.peer.channelId)))
|
||||
}
|
||||
}
|
||||
|
||||
private _story?: Story
|
||||
/**
|
||||
* Story that was posted or edited.
|
||||
*/
|
||||
get story(): Story {
|
||||
if (this._story) return this._story
|
||||
|
||||
assertTypeIs('StoryUpdate.story', this.raw.story, 'storyItem')
|
||||
|
||||
return (this._story = new Story(this.client, this.raw.story, this._peers))
|
||||
}
|
||||
}
|
||||
|
||||
makeInspectable(StoryUpdate)
|
|
@ -9,7 +9,8 @@
|
|||
"scripts": {
|
||||
"test": "mocha -r ts-node/register \"tests/**/*.spec.ts\"",
|
||||
"docs": "typedoc",
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"gen-updates": "node ./scripts/generate.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
|
|
|
@ -19,6 +19,8 @@ import {
|
|||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
DeleteStoryUpdate,
|
||||
TelegramClient,
|
||||
UserStatusUpdate,
|
||||
UserTypingUpdate,
|
||||
|
@ -35,6 +37,7 @@ import {
|
|||
ChatMemberUpdateHandler,
|
||||
ChosenInlineResultHandler,
|
||||
DeleteMessageHandler,
|
||||
DeleteStoryHandler,
|
||||
EditMessageHandler,
|
||||
HistoryReadHandler,
|
||||
InlineQueryHandler,
|
||||
|
@ -43,6 +46,7 @@ import {
|
|||
PollVoteHandler,
|
||||
PreCheckoutQueryHandler,
|
||||
RawUpdateHandler,
|
||||
StoryUpdateHandler,
|
||||
UpdateHandler,
|
||||
UserStatusUpdateHandler,
|
||||
UserTypingHandler,
|
||||
|
@ -1419,5 +1423,57 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
|||
this._addKnownHandler('pre_checkout_query', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a story update handler without any filters
|
||||
*
|
||||
* @param handler Story update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onStoryUpdate(handler: StoryUpdateHandler['callback'], group?: number): void
|
||||
|
||||
/**
|
||||
* Register a story update handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Story update handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onStoryUpdate<Mod>(
|
||||
filter: UpdateFilter<StoryUpdate, Mod>,
|
||||
handler: StoryUpdateHandler<filters.Modify<StoryUpdate, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/** @internal */
|
||||
onStoryUpdate(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('story', filter, handler, group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a delete story handler without any filters
|
||||
*
|
||||
* @param handler Delete story handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onDeleteStory(handler: DeleteStoryHandler['callback'], group?: number): void
|
||||
|
||||
/**
|
||||
* Register a delete story handler with a filter
|
||||
*
|
||||
* @param filter Update filter
|
||||
* @param handler Delete story handler
|
||||
* @param group Handler group index
|
||||
*/
|
||||
onDeleteStory<Mod>(
|
||||
filter: UpdateFilter<DeleteStoryUpdate, Mod>,
|
||||
handler: DeleteStoryHandler<filters.Modify<DeleteStoryUpdate, Mod>>['callback'],
|
||||
group?: number,
|
||||
): void
|
||||
|
||||
/** @internal */
|
||||
onDeleteStory(filter: any, handler?: any, group?: number): void {
|
||||
this._addKnownHandler('delete_story', filter, handler, group)
|
||||
}
|
||||
|
||||
// end-codegen
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
ChatMemberUpdate,
|
||||
ChosenInlineResult,
|
||||
DeleteMessageUpdate,
|
||||
DeleteStoryUpdate,
|
||||
HistoryReadUpdate,
|
||||
InlineQuery,
|
||||
MaybeAsync,
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
PollUpdate,
|
||||
PollVoteUpdate,
|
||||
PreCheckoutQuery,
|
||||
StoryUpdate,
|
||||
TelegramClient,
|
||||
tl,
|
||||
UserStatusUpdate,
|
||||
|
@ -62,6 +64,8 @@ export type BotStoppedHandler<T = BotStoppedUpdate> = ParsedUpdateHandler<'bot_s
|
|||
export type BotChatJoinRequestHandler<T = BotChatJoinRequestUpdate> = ParsedUpdateHandler<'bot_chat_join_request', T>
|
||||
export type ChatJoinRequestHandler<T = ChatJoinRequestUpdate> = ParsedUpdateHandler<'chat_join_request', T>
|
||||
export type PreCheckoutQueryHandler<T = PreCheckoutQuery> = ParsedUpdateHandler<'pre_checkout_query', T>
|
||||
export type StoryUpdateHandler<T = StoryUpdate> = ParsedUpdateHandler<'story', T>
|
||||
export type DeleteStoryHandler<T = DeleteStoryUpdate> = ParsedUpdateHandler<'delete_story', T>
|
||||
|
||||
export type UpdateHandler =
|
||||
| RawUpdateHandler
|
||||
|
@ -81,5 +85,7 @@ export type UpdateHandler =
|
|||
| BotChatJoinRequestHandler
|
||||
| ChatJoinRequestHandler
|
||||
| PreCheckoutQueryHandler
|
||||
| StoryUpdateHandler
|
||||
| DeleteStoryHandler
|
||||
|
||||
// end-codegen
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> TL schema and related utils used for mtcute.
|
||||
|
||||
Generated from TL layer **165** (last updated on 03.10.2023).
|
||||
Generated from TL layer **165** (last updated on 04.10.2023).
|
||||
|
||||
## About
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,6 +10,7 @@
|
|||
"attachMenuBot": ["bot_id"],
|
||||
"autoDownloadSettings": ["video_size_max", "file_size_max"],
|
||||
"botInfo": ["user_id"],
|
||||
"booster": ["user_id"],
|
||||
"channel": ["id"],
|
||||
"channelAdminLogEvent": ["user_id"],
|
||||
"channelAdminLogEventActionChangeLinkedChat": ["prev_value", "new_value"],
|
||||
|
@ -92,6 +93,7 @@
|
|||
"statsGroupTopInviter": ["user_id"],
|
||||
"statsGroupTopPoster": ["user_id"],
|
||||
"storyView": ["user_id"],
|
||||
"storyViews": ["recent_viewers"],
|
||||
"updateBotCallbackQuery": ["user_id"],
|
||||
"updateBotCommands": ["bot_id"],
|
||||
"updateBotInlineQuery": ["user_id"],
|
||||
|
|
Loading…
Reference in a new issue