feat(core): new stickers features
This commit is contained in:
parent
756b99e12f
commit
00da6736e6
11 changed files with 161 additions and 58 deletions
32
packages/core/src/highlevel/methods/stickers/_utils.ts
Normal file
32
packages/core/src/highlevel/methods/stickers/_utils.ts
Normal file
|
@ -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<tl.TypeInputStickerSetItem> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,17 +2,16 @@ import { ITelegramClient } from '../../client.types.js'
|
||||||
import {
|
import {
|
||||||
InputStickerSet,
|
InputStickerSet,
|
||||||
InputStickerSetItem,
|
InputStickerSetItem,
|
||||||
MASK_POSITION_POINT_TO_TL,
|
|
||||||
normalizeInputStickerSet,
|
normalizeInputStickerSet,
|
||||||
StickerSet,
|
StickerSet,
|
||||||
} from '../../types/index.js'
|
} from '../../types/index.js'
|
||||||
import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js'
|
import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js'
|
||||||
|
import { _normalizeInputStickerSetItem } from './_utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a sticker to a sticker set.
|
* Add a sticker to a sticker set.
|
||||||
*
|
*
|
||||||
* Only for bots, and the sticker set must
|
* For bots the sticker set must have been created by this bot.
|
||||||
* have been created by this bot.
|
|
||||||
*
|
*
|
||||||
* @param setId Sticker set short name or TL object with input sticker set
|
* @param setId Sticker set short name or TL object with input sticker set
|
||||||
* @param sticker Sticker to be added
|
* @param sticker Sticker to be added
|
||||||
|
@ -36,20 +35,7 @@ export async function addStickerToSet(
|
||||||
const res = await client.call({
|
const res = await client.call({
|
||||||
_: 'stickers.addStickerToSet',
|
_: 'stickers.addStickerToSet',
|
||||||
stickerset: normalizeInputStickerSet(setId),
|
stickerset: normalizeInputStickerSet(setId),
|
||||||
sticker: {
|
sticker: await _normalizeInputStickerSetItem(client, sticker, params),
|
||||||
_: '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,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return new StickerSet(res)
|
return new StickerSet(res)
|
||||||
|
|
|
@ -5,20 +5,17 @@ import {
|
||||||
InputFileLike,
|
InputFileLike,
|
||||||
InputPeerLike,
|
InputPeerLike,
|
||||||
InputStickerSetItem,
|
InputStickerSetItem,
|
||||||
MASK_POSITION_POINT_TO_TL,
|
|
||||||
StickerSet,
|
StickerSet,
|
||||||
StickerSourceType,
|
StickerSourceType,
|
||||||
StickerType,
|
StickerType,
|
||||||
} from '../../types/index.js'
|
} from '../../types/index.js'
|
||||||
import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js'
|
import { _normalizeFileToDocument } from '../files/normalize-file-to-document.js'
|
||||||
import { resolveUser } from '../users/resolve-peer.js'
|
import { resolveUser } from '../users/resolve-peer.js'
|
||||||
|
import { _normalizeInputStickerSetItem } from './_utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new sticker set.
|
* 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
|
* @param params
|
||||||
* @returns Newly created sticker set
|
* @returns Newly created sticker set
|
||||||
*/
|
*/
|
||||||
|
@ -106,22 +103,7 @@ export async function createStickerSet(
|
||||||
for (const sticker of params.stickers) {
|
for (const sticker of params.stickers) {
|
||||||
const progressCallback = params.progressCallback?.bind(null, i)
|
const progressCallback = params.progressCallback?.bind(null, i)
|
||||||
|
|
||||||
inputStickers.push({
|
inputStickers.push(await _normalizeInputStickerSetItem(client, sticker, { progressCallback }))
|
||||||
_: '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,
|
|
||||||
})
|
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,7 @@ import { fileIdToInputDocument } from '../../utils/convert-file-id.js'
|
||||||
/**
|
/**
|
||||||
* Delete a sticker from a sticker set
|
* Delete a sticker from a sticker set
|
||||||
*
|
*
|
||||||
* Only for bots, and the sticker set must
|
* For bots the sticker set must have been created by this bot.
|
||||||
* have been created by this bot.
|
|
||||||
*
|
*
|
||||||
* @param sticker
|
* @param sticker
|
||||||
* TDLib and Bot API compatible File ID, or a
|
* TDLib and Bot API compatible File ID, or a
|
||||||
|
|
|
@ -9,8 +9,7 @@ import { StickerSet } from '../../types/index.js'
|
||||||
*
|
*
|
||||||
* > **Note**: This method returns *brief* meta information about
|
* > **Note**: This method returns *brief* meta information about
|
||||||
* > the packs, that does not include the stickers themselves.
|
* > the packs, that does not include the stickers themselves.
|
||||||
* > Use {@link StickerSet.getFull} or {@link getStickerSet}
|
* > Use {@link getStickerSet} to get a stickerset that will include the stickers
|
||||||
* > to get a stickerset that will include the stickers
|
|
||||||
*/
|
*/
|
||||||
export async function getInstalledStickers(client: ITelegramClient): Promise<StickerSet[]> {
|
export async function getInstalledStickers(client: ITelegramClient): Promise<StickerSet[]> {
|
||||||
const res = await client.call({
|
const res = await client.call({
|
||||||
|
|
|
@ -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<ArrayPaginated<StickerSet, Long>> {
|
||||||
|
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)
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ import { ITelegramClient } from '../../client.types.js'
|
||||||
import { InputStickerSet, normalizeInputStickerSet, StickerSet } from '../../types/index.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<StickerSet> {
|
export async function getStickerSet(client: ITelegramClient, setId: InputStickerSet): Promise<StickerSet> {
|
||||||
const res = await client.call({
|
const res = await client.call({
|
||||||
|
|
|
@ -9,16 +9,14 @@ import { fileIdToInputDocument } from '../../utils/convert-file-id.js'
|
||||||
* Move a sticker in a sticker set
|
* Move a sticker in a sticker set
|
||||||
* to another position
|
* to another position
|
||||||
*
|
*
|
||||||
* Only for bots, and the sticker set must
|
* For bots the sticker set must have been created by this bot.
|
||||||
* have been created by this bot.
|
|
||||||
*
|
*
|
||||||
* @param sticker
|
* @param sticker
|
||||||
* TDLib and Bot API compatible File ID, or a
|
* TDLib and Bot API compatible File ID, or a
|
||||||
* TL object representing a sticker to be removed
|
* TL object representing a sticker to be removed
|
||||||
* @param position New sticker position (starting from 0)
|
* @param position New sticker position (starting from 0)
|
||||||
* @returns Modfiied sticker set
|
* @returns Modified sticker set
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function moveStickerInSet(
|
export async function moveStickerInSet(
|
||||||
client: ITelegramClient,
|
client: ITelegramClient,
|
||||||
sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument,
|
sticker: string | tdFileId.RawFullRemoteFileLocation | tl.TypeInputDocument,
|
||||||
|
|
|
@ -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<StickerSet> {
|
||||||
|
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)
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import { Voice } from './voice.js'
|
||||||
export type ParsedDocument = Sticker | Voice | Audio | Video | Document
|
export type ParsedDocument = Sticker | Voice | Audio | Video | Document
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function parseDocument(doc: tl.RawDocument, media?: tl.RawMessageMediaDocument): ParsedDocument {
|
export function parseSticker(doc: tl.RawDocument) {
|
||||||
const stickerAttr = doc.attributes.find(
|
const stickerAttr = doc.attributes.find(
|
||||||
(a) => a._ === 'documentAttributeSticker' || a._ === 'documentAttributeCustomEmoji',
|
(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)
|
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) {
|
for (const attr of doc.attributes) {
|
||||||
switch (attr._) {
|
switch (attr._) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { makeInspectable } from '../../utils/index.js'
|
||||||
import { memoizeGetters } from '../../utils/memoize.js'
|
import { memoizeGetters } from '../../utils/memoize.js'
|
||||||
import { MtEmptyError } from '../errors.js'
|
import { MtEmptyError } from '../errors.js'
|
||||||
import { InputFileLike } from '../files/index.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'
|
import { MaskPosition, Sticker, StickerSourceType, StickerType, Thumbnail } from '../media/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,24 +98,38 @@ export interface StickerInfo {
|
||||||
readonly sticker: Sticker
|
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)
|
* A sticker set (aka sticker pack)
|
||||||
*/
|
*/
|
||||||
export class StickerSet {
|
export class StickerSet {
|
||||||
readonly brief: tl.RawStickerSet
|
readonly brief: tl.RawStickerSet
|
||||||
readonly full?: tl.messages.RawStickerSet
|
readonly full?: tl.messages.RawStickerSet
|
||||||
|
readonly cover?: tl.TypeStickerSetCovered
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this object contains information about stickers inside the set
|
* Whether this object contains information about stickers inside the set
|
||||||
*/
|
*/
|
||||||
readonly isFull: boolean
|
readonly isFull: boolean
|
||||||
|
|
||||||
constructor(raw: tl.TypeStickerSet | tl.messages.TypeStickerSet) {
|
constructor(raw: tl.TypeStickerSet | tl.messages.TypeStickerSet | tl.TypeStickerSetCovered) {
|
||||||
if (raw._ === 'messages.stickerSet') {
|
if (raw._ === 'messages.stickerSet') {
|
||||||
this.full = raw
|
this.full = raw
|
||||||
this.brief = raw.set
|
this.brief = raw.set
|
||||||
} else if (raw._ === 'stickerSet') {
|
} else if (raw._ === 'stickerSet') {
|
||||||
this.brief = raw
|
this.brief = raw
|
||||||
|
} else if (tl.isAnyStickerSetCovered(raw)) {
|
||||||
|
this.cover = raw
|
||||||
|
this.brief = raw.set
|
||||||
} else {
|
} else {
|
||||||
throw new MtTypeAssertionError('StickerSet', 'messages.stickerSet | stickerSet', raw._)
|
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)
|
* In case this object does not contain info about stickers (i.e. {@link isFull} = false)
|
||||||
*/
|
*/
|
||||||
get stickers(): ReadonlyArray<StickerInfo> {
|
get stickers(): ReadonlyArray<StickerInfo> {
|
||||||
if (!this.isFull) throw new MtEmptyError()
|
if (!this.full) throw new MtEmptyError()
|
||||||
|
|
||||||
const stickers: StickerInfo[] = []
|
const stickers: StickerInfo[] = []
|
||||||
const index = new LongMap<tl.Mutable<StickerInfo>>()
|
const index = new LongMap<tl.Mutable<StickerInfo>>()
|
||||||
|
|
||||||
this.full!.documents.forEach((doc) => {
|
this.full.documents.forEach((doc) => {
|
||||||
const sticker = parseDocument(doc as tl.RawDocument)
|
const sticker = parseStickerOrThrow(doc as tl.RawDocument)
|
||||||
|
|
||||||
if (!(sticker instanceof Sticker)) {
|
|
||||||
throw new MtTypeAssertionError('full.documents', 'Sticker', sticker.mimeType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const info: tl.Mutable<StickerInfo> = {
|
const info: tl.Mutable<StickerInfo> = {
|
||||||
alt: sticker.emoji,
|
alt: sticker.emoji,
|
||||||
|
@ -243,7 +253,7 @@ export class StickerSet {
|
||||||
index.set(doc.id, info)
|
index.set(doc.id, info)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.full!.packs.forEach((pack) => {
|
this.full.packs.forEach((pack) => {
|
||||||
pack.documents.forEach((id) => {
|
pack.documents.forEach((id) => {
|
||||||
const item = index.get(id)
|
const item = index.get(id)
|
||||||
|
|
||||||
|
@ -256,6 +266,22 @@ export class StickerSet {
|
||||||
return stickers
|
return stickers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cover stickers of the sticker set. Not the same as {@link thumbnails} */
|
||||||
|
get covers(): ReadonlyArray<Sticker> {
|
||||||
|
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.
|
* Available sticker set thumbnails.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue