Skip to main content

rustigram_api/methods/
stickers.rs

1use crate::client::BotClient;
2use crate::error::Result;
3use reqwest::multipart::{Form, Part};
4use rustigram_types::sticker::{
5    InputSticker, MaskPosition, Sticker, StickerFormat, StickerSet, StickerType,
6};
7use serde::Serialize;
8use std::future::{Future, IntoFuture};
9use std::pin::Pin;
10
11#[derive(Serialize)]
12struct GetStickerSetParams {
13    name: String,
14}
15
16/// Builder for the [`getStickerSet`](https://core.telegram.org/bots/api#getstickerset) method.
17pub struct GetStickerSet {
18    client: BotClient,
19    params: GetStickerSetParams,
20}
21impl GetStickerSet {
22    pub(crate) fn new(client: BotClient, name: impl Into<String>) -> Self {
23        Self {
24            client,
25            params: GetStickerSetParams { name: name.into() },
26        }
27    }
28}
29impl IntoFuture for GetStickerSet {
30    type Output = Result<StickerSet>;
31    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
32    fn into_future(self) -> Self::IntoFuture {
33        Box::pin(async move { self.client.post_json("getStickerSet", &self.params).await })
34    }
35}
36
37#[derive(Serialize)]
38struct GetCustomEmojiStickersParams {
39    custom_emoji_ids: Vec<String>,
40}
41
42/// Builder for the [`getCustomEmojiStickers`](https://core.telegram.org/bots/api#getcustomemojistickers) method.
43pub struct GetCustomEmojiStickers {
44    client: BotClient,
45    params: GetCustomEmojiStickersParams,
46}
47impl GetCustomEmojiStickers {
48    pub(crate) fn new(client: BotClient, ids: Vec<impl Into<String>>) -> Self {
49        Self {
50            client,
51            params: GetCustomEmojiStickersParams {
52                custom_emoji_ids: ids.into_iter().map(Into::into).collect(),
53            },
54        }
55    }
56}
57impl IntoFuture for GetCustomEmojiStickers {
58    type Output = Result<Vec<Sticker>>;
59    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
60    fn into_future(self) -> Self::IntoFuture {
61        Box::pin(async move {
62            self.client
63                .post_json("getCustomEmojiStickers", &self.params)
64                .await
65        })
66    }
67}
68
69/// Builder for the [`uploadStickerFile`](https://core.telegram.org/bots/api#uploadstickerfile) method.
70pub struct UploadStickerFile {
71    client: BotClient,
72    user_id: i64,
73    sticker: rustigram_types::file::InputFile,
74    sticker_format: StickerFormat,
75}
76impl UploadStickerFile {
77    pub(crate) fn new(
78        client: BotClient,
79        user_id: i64,
80        sticker: rustigram_types::file::InputFile,
81        format: StickerFormat,
82    ) -> Self {
83        Self {
84            client,
85            user_id,
86            sticker,
87            sticker_format: format,
88        }
89    }
90}
91impl IntoFuture for UploadStickerFile {
92    type Output = Result<rustigram_types::file::File>;
93    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
94    fn into_future(self) -> Self::IntoFuture {
95        Box::pin(async move {
96            match self.sticker {
97                rustigram_types::file::InputFile::Bytes {
98                    filename,
99                    data,
100                    mime_type,
101                } => {
102                    let part = Part::bytes(data)
103                        .file_name(filename)
104                        .mime_str(&mime_type)
105                        .map_err(|e| crate::error::Error::Decode(e.to_string()))?;
106                    let fmt = match self.sticker_format {
107                        StickerFormat::Static => "static",
108                        StickerFormat::Animated => "animated",
109                        StickerFormat::Video => "video",
110                    };
111                    let form = Form::new()
112                        .text("user_id", self.user_id.to_string())
113                        .text("sticker_format", fmt)
114                        .part("sticker", part);
115                    self.client.post_multipart("uploadStickerFile", form).await
116                }
117                ref other => {
118                    let body = serde_json::json!({ "user_id": self.user_id, "sticker": other.as_str(), "sticker_format": self.sticker_format });
119                    self.client.post_json("uploadStickerFile", &body).await
120                }
121            }
122        })
123    }
124}
125
126#[derive(Serialize)]
127struct InputStickerJson {
128    sticker: String,
129    format: StickerFormat,
130    emoji_list: Vec<String>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    mask_position: Option<MaskPosition>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    keywords: Option<Vec<String>>,
135}
136
137#[derive(Serialize)]
138struct CreateNewStickerSetParams {
139    user_id: i64,
140    name: String,
141    title: String,
142    stickers: Vec<InputStickerJson>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    sticker_type: Option<StickerType>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    needs_repainting: Option<bool>,
147}
148
149/// Builder for the [`createNewStickerSet`](https://core.telegram.org/bots/api#createnewstickerset) method.
150pub struct CreateNewStickerSet {
151    client: BotClient,
152    params: CreateNewStickerSetParams,
153}
154impl CreateNewStickerSet {
155    pub(crate) fn new(
156        client: BotClient,
157        user_id: i64,
158        name: impl Into<String>,
159        title: impl Into<String>,
160        stickers: Vec<InputSticker>,
161    ) -> Self {
162        let stickers_json = stickers
163            .into_iter()
164            .map(|s| InputStickerJson {
165                sticker: s.sticker,
166                format: s.format,
167                emoji_list: s.emoji_list,
168                mask_position: s.mask_position,
169                keywords: s.keywords,
170            })
171            .collect();
172        Self {
173            client,
174            params: CreateNewStickerSetParams {
175                user_id,
176                name: name.into(),
177                title: title.into(),
178                stickers: stickers_json,
179                sticker_type: None,
180                needs_repainting: None,
181            },
182        }
183    }
184    /// Sets the sticker set type (`Regular`, `Mask`, or `CustomEmoji`).
185    pub fn sticker_type(mut self, k: StickerType) -> Self {
186        self.params.sticker_type = Some(k);
187        self
188    }
189    /// Enables colour replacement for custom emoji stickers when `true`.
190    pub fn needs_repainting(mut self, v: bool) -> Self {
191        self.params.needs_repainting = Some(v);
192        self
193    }
194}
195impl IntoFuture for CreateNewStickerSet {
196    type Output = Result<bool>;
197    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
198    fn into_future(self) -> Self::IntoFuture {
199        Box::pin(async move {
200            self.client
201                .post_json("createNewStickerSet", &self.params)
202                .await
203        })
204    }
205}
206
207#[derive(Serialize)]
208struct AddStickerToSetParams {
209    user_id: i64,
210    name: String,
211    sticker: InputStickerJson,
212}
213
214/// Builder for the [`addStickerToSet`](https://core.telegram.org/bots/api#addstickertoset) method.
215pub struct AddStickerToSet {
216    client: BotClient,
217    params: AddStickerToSetParams,
218}
219impl AddStickerToSet {
220    pub(crate) fn new(
221        client: BotClient,
222        user_id: i64,
223        name: impl Into<String>,
224        sticker: InputSticker,
225    ) -> Self {
226        Self {
227            client,
228            params: AddStickerToSetParams {
229                user_id,
230                name: name.into(),
231                sticker: InputStickerJson {
232                    sticker: sticker.sticker,
233                    format: sticker.format,
234                    emoji_list: sticker.emoji_list,
235                    mask_position: sticker.mask_position,
236                    keywords: sticker.keywords,
237                },
238            },
239        }
240    }
241}
242impl IntoFuture for AddStickerToSet {
243    type Output = Result<bool>;
244    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
245    fn into_future(self) -> Self::IntoFuture {
246        Box::pin(async move { self.client.post_json("addStickerToSet", &self.params).await })
247    }
248}
249
250macro_rules! simple_sticker_action {
251    ($(#[$doc:meta])* $name:ident, $params_ty:ident { $($f:ident: $t:ty),+ }, $method:literal, $ret:ty) => {
252        #[derive(Serialize)]
253        struct $params_ty { $($f: $t),+ }
254
255        $(#[$doc])*
256        pub struct $name { client: BotClient, params: $params_ty }
257
258        impl IntoFuture for $name {
259            type Output = Result<$ret>;
260            type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
261            fn into_future(self) -> Self::IntoFuture {
262                Box::pin(async move { self.client.post_json($method, &self.params).await })
263            }
264        }
265    };
266}
267
268simple_sticker_action!(
269    /// Builder for the [`setStickerPositionInSet`](https://core.telegram.org/bots/api#setstickerpositioninset) method.
270    SetStickerPositionInSet,
271    SetStickerPositionInSetParams { sticker: String, position: u32 },
272    "setStickerPositionInSet",
273    bool
274);
275impl SetStickerPositionInSet {
276    pub(crate) fn new(client: BotClient, sticker: impl Into<String>, position: u32) -> Self {
277        Self {
278            client,
279            params: SetStickerPositionInSetParams {
280                sticker: sticker.into(),
281                position,
282            },
283        }
284    }
285}
286
287simple_sticker_action!(
288    /// Builder for the [`deleteStickerFromSet`](https://core.telegram.org/bots/api#deletestickerfromset) method.
289    DeleteStickerFromSet,
290    DeleteStickerFromSetParams { sticker: String },
291    "deleteStickerFromSet",
292    bool
293);
294impl DeleteStickerFromSet {
295    pub(crate) fn new(client: BotClient, sticker: impl Into<String>) -> Self {
296        Self {
297            client,
298            params: DeleteStickerFromSetParams {
299                sticker: sticker.into(),
300            },
301        }
302    }
303}
304
305simple_sticker_action!(
306    /// Builder for the [`setStickerSetTitle`](https://core.telegram.org/bots/api#setstickersettitle) method.
307    SetStickerSetTitle,
308    SetStickerSetTitleParams { name: String, title: String },
309    "setStickerSetTitle",
310    bool
311);
312impl SetStickerSetTitle {
313    pub(crate) fn new(
314        client: BotClient,
315        name: impl Into<String>,
316        title: impl Into<String>,
317    ) -> Self {
318        Self {
319            client,
320            params: SetStickerSetTitleParams {
321                name: name.into(),
322                title: title.into(),
323            },
324        }
325    }
326}
327
328simple_sticker_action!(
329    /// Builder for the [`deleteStickerSet`](https://core.telegram.org/bots/api#deletestickerset) method.
330    DeleteStickerSet,
331    DeleteStickerSetParams { name: String },
332    "deleteStickerSet",
333    bool
334);
335impl DeleteStickerSet {
336    pub(crate) fn new(client: BotClient, name: impl Into<String>) -> Self {
337        Self {
338            client,
339            params: DeleteStickerSetParams { name: name.into() },
340        }
341    }
342}
343
344#[derive(Serialize)]
345struct SetStickerEmojiListParams {
346    sticker: String,
347    emoji_list: Vec<String>,
348}
349
350/// Builder for the [`setStickerEmojiList`](https://core.telegram.org/bots/api#setstickeremojilist) method.
351pub struct SetStickerEmojiList {
352    client: BotClient,
353    params: SetStickerEmojiListParams,
354}
355impl SetStickerEmojiList {
356    pub(crate) fn new(
357        client: BotClient,
358        sticker: impl Into<String>,
359        emoji_list: Vec<impl Into<String>>,
360    ) -> Self {
361        Self {
362            client,
363            params: SetStickerEmojiListParams {
364                sticker: sticker.into(),
365                emoji_list: emoji_list.into_iter().map(Into::into).collect(),
366            },
367        }
368    }
369}
370impl IntoFuture for SetStickerEmojiList {
371    type Output = Result<bool>;
372    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
373    fn into_future(self) -> Self::IntoFuture {
374        Box::pin(async move {
375            self.client
376                .post_json("setStickerEmojiList", &self.params)
377                .await
378        })
379    }
380}
381
382#[derive(Serialize)]
383struct SetStickerKeywordsParams {
384    sticker: String,
385    #[serde(skip_serializing_if = "Option::is_none")]
386    keywords: Option<Vec<String>>,
387}
388
389/// Builder for the [`setStickerKeywords`](https://core.telegram.org/bots/api#setstickerkeywords) method.
390pub struct SetStickerKeywords {
391    client: BotClient,
392    params: SetStickerKeywordsParams,
393}
394impl SetStickerKeywords {
395    pub(crate) fn new(client: BotClient, sticker: impl Into<String>) -> Self {
396        Self {
397            client,
398            params: SetStickerKeywordsParams {
399                sticker: sticker.into(),
400                keywords: None,
401            },
402        }
403    }
404    /// Sets the search keywords for this sticker (up to 20 words).
405    pub fn keywords(mut self, kw: Vec<impl Into<String>>) -> Self {
406        self.params.keywords = Some(kw.into_iter().map(Into::into).collect());
407        self
408    }
409}
410impl IntoFuture for SetStickerKeywords {
411    type Output = Result<bool>;
412    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
413    fn into_future(self) -> Self::IntoFuture {
414        Box::pin(async move {
415            self.client
416                .post_json("setStickerKeywords", &self.params)
417                .await
418        })
419    }
420}
421
422#[derive(Serialize)]
423struct SetStickerMaskPositionParams {
424    sticker: String,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    mask_position: Option<MaskPosition>,
427}
428
429/// Builder for the [`setStickerMaskPosition`](https://core.telegram.org/bots/api#setstickermaskposition) method.
430pub struct SetStickerMaskPosition {
431    client: BotClient,
432    params: SetStickerMaskPositionParams,
433}
434impl SetStickerMaskPosition {
435    pub(crate) fn new(client: BotClient, sticker: impl Into<String>) -> Self {
436        Self {
437            client,
438            params: SetStickerMaskPositionParams {
439                sticker: sticker.into(),
440                mask_position: None,
441            },
442        }
443    }
444    /// Sets the mask position for this mask sticker.
445    pub fn mask_position(mut self, mp: MaskPosition) -> Self {
446        self.params.mask_position = Some(mp);
447        self
448    }
449}
450impl IntoFuture for SetStickerMaskPosition {
451    type Output = Result<bool>;
452    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
453    fn into_future(self) -> Self::IntoFuture {
454        Box::pin(async move {
455            self.client
456                .post_json("setStickerMaskPosition", &self.params)
457                .await
458        })
459    }
460}
461
462/// Builder for the [`getForumTopicIconStickers`](https://core.telegram.org/bots/api#getforumtopiciconstickers) method.
463pub struct GetForumTopicIconStickers {
464    client: BotClient,
465}
466impl GetForumTopicIconStickers {
467    pub(crate) fn new(client: BotClient) -> Self {
468        Self { client }
469    }
470}
471impl IntoFuture for GetForumTopicIconStickers {
472    type Output = Result<Vec<Sticker>>;
473    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
474    fn into_future(self) -> Self::IntoFuture {
475        Box::pin(async move {
476            self.client
477                .post_json("getForumTopicIconStickers", &serde_json::json!({}))
478                .await
479        })
480    }
481}
482
483// ─── replaceStickerInSet ──────────────────────────────────────────────────────
484
485#[derive(Serialize)]
486struct ReplaceStickerInSetParams {
487    user_id: i64,
488    name: String,
489    old_sticker: String,
490    sticker: InputStickerJson,
491}
492
493/// Builder for the [`replaceStickerInSet`](https://core.telegram.org/bots/api#replacestickerinset) method.
494///
495/// Equivalent to calling `deleteStickerFromSet`, `addStickerToSet`, and
496/// `setStickerPositionInSet` in sequence.
497pub struct ReplaceStickerInSet {
498    client: BotClient,
499    params: ReplaceStickerInSetParams,
500}
501
502impl ReplaceStickerInSet {
503    pub(crate) fn new(
504        client: BotClient,
505        user_id: i64,
506        name: impl Into<String>,
507        old_sticker: impl Into<String>,
508        sticker: InputSticker,
509    ) -> Self {
510        Self {
511            client,
512            params: ReplaceStickerInSetParams {
513                user_id,
514                name: name.into(),
515                old_sticker: old_sticker.into(),
516                sticker: InputStickerJson {
517                    sticker: sticker.sticker,
518                    format: sticker.format,
519                    emoji_list: sticker.emoji_list,
520                    mask_position: sticker.mask_position,
521                    keywords: sticker.keywords,
522                },
523            },
524        }
525    }
526}
527
528impl IntoFuture for ReplaceStickerInSet {
529    type Output = Result<bool>;
530    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
531    fn into_future(self) -> Self::IntoFuture {
532        Box::pin(async move {
533            self.client
534                .post_json("replaceStickerInSet", &self.params)
535                .await
536        })
537    }
538}
539
540// ─── setStickerSetThumbnail ───────────────────────────────────────────────────
541
542/// Builder for the [`setStickerSetThumbnail`](https://core.telegram.org/bots/api#setstickersetthumbnail) method.
543///
544/// Sets the thumbnail of a regular or mask sticker set.
545/// The thumbnail format must match the sticker format in the set.
546/// Omit `thumbnail` to drop the current thumbnail and use the first sticker instead.
547pub struct SetStickerSetThumbnail {
548    client: BotClient,
549    name: String,
550    user_id: i64,
551    /// The thumbnail format string: `"static"`, `"animated"`, or `"video"`.
552    format: String,
553    thumbnail: Option<rustigram_types::file::InputFile>,
554}
555
556impl SetStickerSetThumbnail {
557    pub(crate) fn new(
558        client: BotClient,
559        name: impl Into<String>,
560        user_id: i64,
561        format: impl Into<String>,
562    ) -> Self {
563        Self {
564            client,
565            name: name.into(),
566            user_id,
567            format: format.into(),
568            thumbnail: None,
569        }
570    }
571    /// Sets the thumbnail file to upload. Omit to remove the current thumbnail.
572    pub fn thumbnail(mut self, f: rustigram_types::file::InputFile) -> Self {
573        self.thumbnail = Some(f);
574        self
575    }
576}
577
578impl IntoFuture for SetStickerSetThumbnail {
579    type Output = Result<bool>;
580    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
581    fn into_future(self) -> Self::IntoFuture {
582        Box::pin(async move {
583            match self.thumbnail {
584                Some(rustigram_types::file::InputFile::Bytes {
585                    filename,
586                    data,
587                    mime_type,
588                }) => {
589                    let part = Part::bytes(data)
590                        .file_name(filename)
591                        .mime_str(&mime_type)
592                        .map_err(|e| crate::error::Error::Decode(e.to_string()))?;
593                    let form = Form::new()
594                        .text("name", self.name)
595                        .text("user_id", self.user_id.to_string())
596                        .text("format", self.format)
597                        .part("thumbnail", part);
598                    self.client
599                        .post_multipart("setStickerSetThumbnail", form)
600                        .await
601                }
602                other => {
603                    let mut body = serde_json::json!({
604                        "name": self.name,
605                        "user_id": self.user_id,
606                        "format": self.format,
607                    });
608                    if let Some(f) = other {
609                        body["thumbnail"] = serde_json::json!(f.as_str());
610                    }
611                    self.client.post_json("setStickerSetThumbnail", &body).await
612                }
613            }
614        })
615    }
616}
617
618// ─── setCustomEmojiStickerSetThumbnail ────────────────────────────────────────
619
620#[derive(Serialize)]
621struct SetCustomEmojiStickerSetThumbnailParams {
622    name: String,
623    #[serde(skip_serializing_if = "Option::is_none")]
624    custom_emoji_id: Option<String>,
625}
626
627/// Builder for the [`setCustomEmojiStickerSetThumbnail`](https://core.telegram.org/bots/api#setcustomemojistickersetthumbnail) method.
628///
629/// Sets the thumbnail of a custom emoji sticker set.
630/// Omit `custom_emoji_id` or pass an empty string to drop the current thumbnail
631/// and use the first sticker instead.
632pub struct SetCustomEmojiStickerSetThumbnail {
633    client: BotClient,
634    params: SetCustomEmojiStickerSetThumbnailParams,
635}
636
637impl SetCustomEmojiStickerSetThumbnail {
638    pub(crate) fn new(client: BotClient, name: impl Into<String>) -> Self {
639        Self {
640            client,
641            params: SetCustomEmojiStickerSetThumbnailParams {
642                name: name.into(),
643                custom_emoji_id: None,
644            },
645        }
646    }
647    /// Sets the custom emoji identifier to use as the thumbnail.
648    /// Pass an empty string to remove the current thumbnail.
649    pub fn custom_emoji_id(mut self, id: impl Into<String>) -> Self {
650        self.params.custom_emoji_id = Some(id.into());
651        self
652    }
653}
654
655impl IntoFuture for SetCustomEmojiStickerSetThumbnail {
656    type Output = Result<bool>;
657    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
658    fn into_future(self) -> Self::IntoFuture {
659        Box::pin(async move {
660            self.client
661                .post_json("setCustomEmojiStickerSetThumbnail", &self.params)
662                .await
663        })
664    }
665}