diff --git a/packages/core/src/highlevel/methods/stickers/_utils.ts b/packages/core/src/highlevel/methods/stickers/_utils.ts new file mode 100644 index 00000000..e446b78e --- /dev/null +++ b/packages/core/src/highlevel/methods/stickers/_utils.ts @@ -0,0 +1,32 @@ +import { tl } from '@mtcute/tl' + +import { ITelegramClient } from '../../client.types.js' +import { InputStickerSetItem, MASK_POSITION_POINT_TO_TL } from '../../types/index.js' +import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js' + +/** + * @internal + * @noemit + */ +export async function _normalizeInputStickerSetItem( + client: ITelegramClient, + sticker: InputStickerSetItem, + params?: { + progressCallback?: (uploaded: number, total: number) => void + }, +): Promise { + return { + _: 'inputStickerSetItem', + document: await _normalizeFileToDocument(client, sticker.file, params ?? {}), + emoji: sticker.emojis, + maskCoords: sticker.maskPosition ? + { + _: 'maskCoords', + n: MASK_POSITION_POINT_TO_TL[sticker.maskPosition.point], + x: sticker.maskPosition.x, + y: sticker.maskPosition.y, + zoom: sticker.maskPosition.scale, + } : + undefined, + } +} diff --git a/packages/core/src/highlevel/methods/stickers/add-sticker-to-set.ts b/packages/core/src/highlevel/methods/stickers/add-sticker-to-set.ts index d2ccd9bf..e94ba0e3 100644 --- a/packages/core/src/highlevel/methods/stickers/add-sticker-to-set.ts +++ b/packages/core/src/highlevel/methods/stickers/add-sticker-to-set.ts @@ -2,17 +2,16 @@ import { ITelegramClient } from '../../client.types.js' import { InputStickerSet, InputStickerSetItem, - MASK_POSITION_POINT_TO_TL, normalizeInputStickerSet, StickerSet, } from '../../types/index.js' import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js' +import { _normalizeInputStickerSetItem } from './_utils.js' /** * Add a sticker to a sticker set. * - * Only for bots, and the sticker set must - * have been created by this bot. + * For bots the sticker set must have been created by this bot. * * @param setId Sticker set short name or TL object with input sticker set * @param sticker Sticker to be added @@ -36,20 +35,7 @@ export async function addStickerToSet( const res = await client.call({ _: 'stickers.addStickerToSet', stickerset: normalizeInputStickerSet(setId), - sticker: { - _: 'inputStickerSetItem', - document: await _normalizeFileToDocument(client, sticker.file, params ?? {}), - emoji: sticker.emojis, - maskCoords: sticker.maskPosition ? - { - _: 'maskCoords', - n: MASK_POSITION_POINT_TO_TL[sticker.maskPosition.point], - x: sticker.maskPosition.x, - y: sticker.maskPosition.y, - zoom: sticker.maskPosition.scale, - } : - undefined, - }, + sticker: await _normalizeInputStickerSetItem(client, sticker, params), }) return new StickerSet(res) diff --git a/packages/core/src/highlevel/methods/stickers/create-sticker-set.ts b/packages/core/src/highlevel/methods/stickers/create-sticker-set.ts index 0b00e251..2c86201b 100644 --- a/packages/core/src/highlevel/methods/stickers/create-sticker-set.ts +++ b/packages/core/src/highlevel/methods/stickers/create-sticker-set.ts @@ -5,20 +5,17 @@ import { InputFileLike, InputPeerLike, InputStickerSetItem, - MASK_POSITION_POINT_TO_TL, StickerSet, StickerSourceType, StickerType, } from '../../types/index.js' import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js' import { resolveUser } from '../users/resolve-peer.js' +import { _normalizeInputStickerSetItem } from './_utils.js' /** * Create a new sticker set. * - * This is the only sticker-related method that - * users can use (they allowed it with the "import stickers" update) - * * @param params * @returns Newly created sticker set */ @@ -106,22 +103,7 @@ export async function createStickerSet( for (const sticker of params.stickers) { const progressCallback = params.progressCallback?.bind(null, i) - inputStickers.push({ - _: 'inputStickerSetItem', - document: await _normalizeFileToDocument(client, sticker.file, { - progressCallback, - }), - emoji: sticker.emojis, - maskCoords: sticker.maskPosition ? - { - _: 'maskCoords', - n: MASK_POSITION_POINT_TO_TL[sticker.maskPosition.point], - x: sticker.maskPosition.x, - y: sticker.maskPosition.y, - zoom: sticker.maskPosition.scale, - } : - undefined, - }) + inputStickers.push(await _normalizeInputStickerSetItem(client, sticker, { progressCallback })) i += 1 } diff --git a/packages/core/src/highlevel/methods/stickers/delete-sticker-from-set.ts b/packages/core/src/highlevel/methods/stickers/delete-sticker-from-set.ts index 2ba7f0be..0d83108f 100644 --- a/packages/core/src/highlevel/methods/stickers/delete-sticker-from-set.ts +++ b/packages/core/src/highlevel/methods/stickers/delete-sticker-from-set.ts @@ -8,8 +8,7 @@ import { fileIdToInputDocument } from '../../utils/convert-file-id.js' /** * Delete a sticker from a sticker set * - * Only for bots, and the sticker set must - * have been created by this bot. + * For bots the sticker set must have been created by this bot. * * @param sticker * TDLib and Bot API compatible File ID, or a diff --git a/packages/core/src/highlevel/methods/stickers/get-installed-stickers.ts b/packages/core/src/highlevel/methods/stickers/get-installed-stickers.ts index 5f310198..05691085 100644 --- a/packages/core/src/highlevel/methods/stickers/get-installed-stickers.ts +++ b/packages/core/src/highlevel/methods/stickers/get-installed-stickers.ts @@ -9,8 +9,7 @@ import { StickerSet } from '../../types/index.js' * * > **Note**: This method returns *brief* meta information about * > the packs, that does not include the stickers themselves. - * > Use {@link StickerSet.getFull} or {@link getStickerSet} - * > to get a stickerset that will include the stickers + * > Use {@link getStickerSet} to get a stickerset that will include the stickers */ export async function getInstalledStickers(client: ITelegramClient): Promise { const res = await client.call({ diff --git a/packages/core/src/highlevel/methods/stickers/get-my-sticker-sets.ts b/packages/core/src/highlevel/methods/stickers/get-my-sticker-sets.ts new file mode 100644 index 00000000..25e492f1 --- /dev/null +++ b/packages/core/src/highlevel/methods/stickers/get-my-sticker-sets.ts @@ -0,0 +1,30 @@ +import Long from 'long' + +import { ITelegramClient } from '../../client.types.js' +import { StickerSet } from '../../types/misc/sticker-set.js' +import { ArrayPaginated } from '../../types/utils.js' +import { makeArrayPaginated } from '../../utils/misc-utils.js' + +// @available=user +/** + * Get the list of sticker sets that were created by the current user + */ +export async function getMyStickerSets( + client: ITelegramClient, + params?: { + /** Offset for pagination */ + offset?: Long + /** Limit for pagination */ + limit?: number + }, +): Promise> { + const res = await client.call({ + _: 'messages.getMyStickers', + offsetId: params?.offset ?? Long.ZERO, + limit: params?.limit ?? 100, + }) + + const items = res.sets.map((x) => new StickerSet(x)) + + return makeArrayPaginated(items, res.count, items[items.length - 1]?.brief.id) +} diff --git a/packages/core/src/highlevel/methods/stickers/get-sticker-set.ts b/packages/core/src/highlevel/methods/stickers/get-sticker-set.ts index 8b1f616a..b18321dd 100644 --- a/packages/core/src/highlevel/methods/stickers/get-sticker-set.ts +++ b/packages/core/src/highlevel/methods/stickers/get-sticker-set.ts @@ -2,9 +2,9 @@ import { ITelegramClient } from '../../client.types.js' import { InputStickerSet, normalizeInputStickerSet, StickerSet } from '../../types/index.js' /** - * Get a sticker pack and stickers inside of it. + * Get a sticker set and stickers inside of it. * - * @param setId Sticker pack short name, dice emoji, `"emoji"` for animated emojis or input ID + * @param setId Sticker set identifier */ export async function getStickerSet(client: ITelegramClient, setId: InputStickerSet): Promise { const res = await client.call({ diff --git a/packages/core/src/highlevel/methods/stickers/move-sticker-in-set.ts b/packages/core/src/highlevel/methods/stickers/move-sticker-in-set.ts index 24114d27..0b4642d3 100644 --- a/packages/core/src/highlevel/methods/stickers/move-sticker-in-set.ts +++ b/packages/core/src/highlevel/methods/stickers/move-sticker-in-set.ts @@ -9,16 +9,14 @@ import { fileIdToInputDocument } from '../../utils/convert-file-id.js' * Move a sticker in a sticker set * to another position * - * Only for bots, and the sticker set must - * have been created by this bot. + * For bots the sticker set must have been created by this bot. * * @param sticker * TDLib and Bot API compatible File ID, or a * TL object representing a sticker to be removed * @param position New sticker position (starting from 0) - * @returns Modfiied sticker set + * @returns Modified sticker set */ - export async function moveStickerInSet( client: ITelegramClient, sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument, diff --git a/packages/core/src/highlevel/methods/stickers/replace-sticker-in-set.ts b/packages/core/src/highlevel/methods/stickers/replace-sticker-in-set.ts new file mode 100644 index 00000000..1cfc5c33 --- /dev/null +++ b/packages/core/src/highlevel/methods/stickers/replace-sticker-in-set.ts @@ -0,0 +1,45 @@ +import { tdFileId } from '@mtcute/file-id' +import { tl } from '@mtcute/tl' + +import { ITelegramClient } from '../../client.types.js' +import { InputStickerSetItem, StickerSet } from '../../types/index.js' +import { fileIdToInputDocument } from '../../utils/convert-file-id.js' +import { _normalizeInputStickerSetItem } from './_utils.js' + +/** + * Replace a sticker in a sticker set with another sticker + * + * For bots the sticker set must have been created by this bot. + * + * @param sticker + * TDLib and Bot API compatible File ID, or a + * TL object representing a sticker to be removed + * @param newSticker New sticker to replace the old one with + * @returns Modfiied sticker set + */ +export async function replaceStickerInSet( + client: ITelegramClient, + sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument, + newSticker: InputStickerSetItem, + params?: { + /** + * Upload progress callback + * + * @param uploaded Number of bytes uploaded + * @param total Total file size + */ + progressCallback?: (uploaded: number, total: number) => void + }, +): Promise { + if (tdFileId.isFileIdLike(sticker)) { + sticker = fileIdToInputDocument(sticker) + } + + const res = await client.call({ + _: 'stickers.replaceSticker', + sticker, + newSticker: await _normalizeInputStickerSetItem(client, newSticker, params), + }) + + return new StickerSet(res) +} diff --git a/packages/core/src/highlevel/types/media/document-utils.ts b/packages/core/src/highlevel/types/media/document-utils.ts index b1c88b0f..382ac415 100644 --- a/packages/core/src/highlevel/types/media/document-utils.ts +++ b/packages/core/src/highlevel/types/media/document-utils.ts @@ -9,7 +9,7 @@ import { Voice } from './voice.js' export type ParsedDocument = Sticker | Voice | Audio | Video | Document /** @internal */ -export function parseDocument(doc: tl.RawDocument, media?: tl.RawMessageMediaDocument): ParsedDocument { +export function parseSticker(doc: tl.RawDocument) { const stickerAttr = doc.attributes.find( (a) => a._ === 'documentAttributeSticker' || a._ === 'documentAttributeCustomEmoji', ) @@ -21,6 +21,12 @@ export function parseDocument(doc: tl.RawDocument, media?: tl.RawMessageMediaDoc return new Sticker(doc, stickerAttr as tl.RawDocumentAttributeSticker | tl.RawDocumentAttributeCustomEmoji, sz) } +} + +/** @internal */ +export function parseDocument(doc: tl.RawDocument, media?: tl.RawMessageMediaDocument): ParsedDocument { + const sticker = parseSticker(doc) + if (sticker) return sticker for (const attr of doc.attributes) { switch (attr._) { diff --git a/packages/core/src/highlevel/types/misc/sticker-set.ts b/packages/core/src/highlevel/types/misc/sticker-set.ts index fcb7a922..b0ca4b25 100644 --- a/packages/core/src/highlevel/types/misc/sticker-set.ts +++ b/packages/core/src/highlevel/types/misc/sticker-set.ts @@ -6,7 +6,7 @@ import { makeInspectable } from '../../utils/index.js' import { memoizeGetters } from '../../utils/memoize.js' import { MtEmptyError } from '../errors.js' import { InputFileLike } from '../files/index.js' -import { parseDocument } from '../media/document-utils.js' +import { parseSticker } from '../media/document-utils.js' import { MaskPosition, Sticker, StickerSourceType, StickerType, Thumbnail } from '../media/index.js' /** @@ -98,24 +98,38 @@ export interface StickerInfo { readonly sticker: Sticker } +function parseStickerOrThrow(doc: tl.RawDocument): Sticker { + const sticker = parseSticker(doc) + + if (!sticker) { + throw new MtTypeAssertionError('full.documents', 'sticker', 'not a sticker') + } + + return sticker +} + /** * A sticker set (aka sticker pack) */ export class StickerSet { readonly brief: tl.RawStickerSet readonly full?: tl.messages.RawStickerSet + readonly cover?: tl.TypeStickerSetCovered /** * Whether this object contains information about stickers inside the set */ readonly isFull: boolean - constructor(raw: tl.TypeStickerSet | tl.messages.TypeStickerSet) { + constructor(raw: tl.TypeStickerSet | tl.messages.TypeStickerSet | tl.TypeStickerSetCovered) { if (raw._ === 'messages.stickerSet') { this.full = raw this.brief = raw.set } else if (raw._ === 'stickerSet') { this.brief = raw + } else if (tl.isAnyStickerSetCovered(raw)) { + this.cover = raw + this.brief = raw.set } else { throw new MtTypeAssertionError('StickerSet', 'messages.stickerSet | stickerSet', raw._) } @@ -222,17 +236,13 @@ export class StickerSet { * In case this object does not contain info about stickers (i.e. {@link isFull} = false) */ get stickers(): ReadonlyArray { - if (!this.isFull) throw new MtEmptyError() + if (!this.full) throw new MtEmptyError() const stickers: StickerInfo[] = [] const index = new LongMap>() - this.full!.documents.forEach((doc) => { - const sticker = parseDocument(doc as tl.RawDocument) - - if (!(sticker instanceof Sticker)) { - throw new MtTypeAssertionError('full.documents', 'Sticker', sticker.mimeType) - } + this.full.documents.forEach((doc) => { + const sticker = parseStickerOrThrow(doc as tl.RawDocument) const info: tl.Mutable = { alt: sticker.emoji, @@ -243,7 +253,7 @@ export class StickerSet { index.set(doc.id, info) }) - this.full!.packs.forEach((pack) => { + this.full.packs.forEach((pack) => { pack.documents.forEach((id) => { const item = index.get(id) @@ -256,6 +266,22 @@ export class StickerSet { return stickers } + /** Cover stickers of the sticker set. Not the same as {@link thumbnails} */ + get covers(): ReadonlyArray { + if (!this.cover) return [] + + switch (this.cover._) { + case 'stickerSetCovered': + return [parseStickerOrThrow(this.cover.cover as tl.RawDocument)] + case 'stickerSetMultiCovered': + return this.cover.covers.map((it) => parseStickerOrThrow(it as tl.RawDocument)) + case 'stickerSetFullCovered': + return this.cover.documents.map((it) => parseStickerOrThrow(it as tl.RawDocument)) + case 'stickerSetNoCovered': + return [] + } + } + /** * Available sticker set thumbnails. *