tgbot/types/definitions/media/
paid.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    api::{Form, Method, Payload},
5    types::{
6        ChatId,
7        InputPaidMediaGroup,
8        Integer,
9        Message,
10        ParseMode,
11        PhotoSize,
12        ReplyMarkup,
13        ReplyMarkupError,
14        ReplyParameters,
15        ReplyParametersError,
16        SuggestedPostParameters,
17        SuggestedPostParametersError,
18        TextEntities,
19        TextEntity,
20        TextEntityError,
21        User,
22        Video,
23    },
24};
25
26/// Contains information about a paid media purchase.
27#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
28pub struct PaidMediaPurchased {
29    /// User who purchased the media.
30    pub from: User,
31    /// Bot-specified paid media payload.
32    #[serde(rename = "paid_media_payload")]
33    pub payload: String,
34}
35
36impl PaidMediaPurchased {
37    /// Creates a new `PaidMediaPurchased`.
38    ///
39    /// # Arguments
40    ///
41    /// * `from` - User who purchased the media.
42    /// * `payload` - Bot-specified paid media payload.
43    pub fn new<T>(from: User, payload: T) -> Self
44    where
45        T: Into<String>,
46    {
47        Self {
48            from,
49            payload: payload.into(),
50        }
51    }
52}
53
54/// Describes the paid media added to a message.
55#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
56pub struct PaidMediaInfo {
57    /// The number of Telegram Stars that must be paid to buy access to the media.
58    pub star_count: Integer,
59    /// Information about the paid media.
60    pub paid_media: Vec<PaidMedia>,
61}
62
63impl PaidMediaInfo {
64    /// Creates a new `PaidMediaInfo`.
65    ///
66    /// # Arguments
67    ///
68    /// * `star_count` - The number of Telegram Stars that must be paid to buy access to the media.
69    /// * `paid_media` - Information about the paid media.
70    pub fn new<A, B>(star_count: Integer, paid_media: A) -> Self
71    where
72        A: IntoIterator<Item = B>,
73        B: Into<PaidMedia>,
74    {
75        Self {
76            star_count,
77            paid_media: paid_media.into_iter().map(Into::into).collect(),
78        }
79    }
80}
81
82/// Describes paid media.
83#[derive(Clone, Debug, derive_more::From, Deserialize, PartialEq, PartialOrd, Serialize)]
84#[serde(from = "RawPaidMedia", into = "RawPaidMedia")]
85#[allow(clippy::large_enum_variant)]
86pub enum PaidMedia {
87    /// The paid media is a photo.
88    Photo(Vec<PhotoSize>),
89    /// The paid media isn't available before the payment.
90    Preview(PaidMediaPreview),
91    /// The paid media is a video.
92    Video(Video),
93}
94
95/// The paid media isn't available before the payment.
96#[serde_with::skip_serializing_none]
97#[derive(Clone, Debug, Default, Deserialize, PartialEq, PartialOrd, Serialize)]
98pub struct PaidMediaPreview {
99    /// Duration of the media in seconds as defined by the sender.
100    pub duration: Option<Integer>,
101    /// Media height as defined by the sender.
102    pub height: Option<Integer>,
103    /// Media width as defined by the sender.
104    pub width: Option<Integer>,
105}
106
107impl PaidMediaPreview {
108    /// Sets a new duration.
109    ///
110    /// # Arguments
111    ///
112    /// * `value` - Duration of the media in seconds as defined by the sender.
113    pub fn with_duration(mut self, value: Integer) -> Self {
114        self.duration = Some(value);
115        self
116    }
117
118    /// Sets a new height.
119    ///
120    /// # Arguments
121    ///
122    /// * `value` - Height of the media in seconds as defined by the sender.
123    pub fn with_height(mut self, value: Integer) -> Self {
124        self.height = Some(value);
125        self
126    }
127
128    /// Sets a new width.
129    ///
130    /// # Arguments
131    ///
132    /// * `value` - Width of the media in seconds as defined by the sender.
133    pub fn with_width(mut self, value: Integer) -> Self {
134        self.width = Some(value);
135        self
136    }
137}
138
139#[serde_with::skip_serializing_none]
140#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
141#[serde(rename_all = "snake_case", tag = "type")]
142#[allow(clippy::large_enum_variant)]
143enum RawPaidMedia {
144    Photo {
145        photo: Vec<PhotoSize>,
146    },
147    Preview {
148        duration: Option<Integer>,
149        height: Option<Integer>,
150        width: Option<Integer>,
151    },
152    Video {
153        video: Video,
154    },
155}
156
157impl From<RawPaidMedia> for PaidMedia {
158    fn from(value: RawPaidMedia) -> Self {
159        match value {
160            RawPaidMedia::Photo { photo } => Self::Photo(photo),
161            RawPaidMedia::Preview {
162                duration,
163                height,
164                width,
165            } => Self::Preview(PaidMediaPreview {
166                duration,
167                height,
168                width,
169            }),
170            RawPaidMedia::Video { video } => Self::Video(video),
171        }
172    }
173}
174
175impl From<PaidMedia> for RawPaidMedia {
176    fn from(value: PaidMedia) -> Self {
177        match value {
178            PaidMedia::Photo(photo) => Self::Photo { photo },
179            PaidMedia::Preview(PaidMediaPreview {
180                duration,
181                height,
182                width,
183            }) => Self::Preview {
184                duration,
185                height,
186                width,
187            },
188            PaidMedia::Video(video) => Self::Video { video },
189        }
190    }
191}
192
193/// Send paid media to channel chats.
194#[derive(Debug)]
195pub struct SendPaidMedia {
196    form: Form,
197}
198
199impl SendPaidMedia {
200    /// Creates a new `SendPaidMedia`.
201    ///
202    /// # Arguments
203    ///
204    /// * `chat_id` - Unique identifier for the target chat.
205    /// * `media` - An array describing the media to be sent
206    /// * `star_count` - The number of Telegram Stars that must be paid to buy access to the media; 1-25000.
207    pub fn new<T>(chat_id: T, media: InputPaidMediaGroup, star_count: Integer) -> Self
208    where
209        T: Into<ChatId>,
210    {
211        let mut form: Form = media.into();
212        form.insert_field("chat_id", chat_id.into());
213        form.insert_field("star_count", star_count);
214        Self { form }
215    }
216
217    /// Sets a new value for the `allow_paid_broadcast` flag.
218    ///
219    /// # Arguments
220    ///
221    /// * `value` - Whether to allow up to 1000 messages per second, ignoring broadcasting limits
222    ///   for a fee of 0.1 Telegram Stars per message.
223    ///   The relevant Stars will be withdrawn from the bot's balance.
224    pub fn with_allow_paid_broadcast(mut self, value: bool) -> Self {
225        self.form.insert_field("allow_paid_broadcast", value);
226        self
227    }
228
229    /// Sets a new business connection ID.
230    ///
231    /// # Arguments
232    ///
233    /// * `value` - Unique identifier of the business connection
234    ///   on behalf of which the message will be sent.
235    pub fn with_business_connection_id<T>(mut self, value: T) -> Self
236    where
237        T: Into<String>,
238    {
239        self.form.insert_field("business_connection_id", value.into());
240        self
241    }
242
243    /// Sets a new caption.
244    ///
245    /// # Arguments
246    ///
247    /// `value` - Media caption, 0-1024 characters after entities parsing.
248    pub fn with_caption<T>(mut self, value: T) -> Self
249    where
250        T: Into<String>,
251    {
252        self.form.insert_field("caption", value.into());
253        self
254    }
255
256    /// Sets a new list of caption entities.
257    ///
258    /// # Arguments
259    ///
260    /// `value` - A list of special entities that appear in the caption, which can be specified instead of parse_mode.
261    pub fn with_caption_entities<T>(mut self, value: T) -> Result<Self, TextEntityError>
262    where
263        T: IntoIterator<Item = TextEntity>,
264    {
265        let value = value.into_iter().collect::<TextEntities>().serialize()?;
266        self.form.insert_field("caption_entities", value);
267        self.form.remove_field("parse_mode");
268        Ok(self)
269    }
270
271    /// Sets a new direct messages topic ID
272    ///
273    /// * `value` - Identifier of the direct messages topic to which the message will be sent.
274    ///
275    /// Required if the message is sent to a direct messages chat.
276    pub fn with_direct_messages_topic_id(mut self, value: Integer) -> Self {
277        self.form.insert_field("direct_messages_topic_id", value);
278        self
279    }
280
281    /// Sets a new value for the `disable_notification` flag.
282    ///
283    /// # Arguments
284    ///
285    /// `value` - Whether to send the message silently.
286    ///
287    /// Users will receive a notification with no sound.
288    pub fn with_disable_notification(mut self, value: bool) -> Self {
289        self.form.insert_field("disable_notification", value);
290        self
291    }
292
293    /// Sets a new message thread ID.
294    ///
295    /// # Arguments
296    ///
297    /// * `value` - Unique identifier of the target message thread;
298    ///   for forum supergroups and private chats of bots with forum topic mode enabled only.
299    pub fn with_message_thread_id(mut self, value: Integer) -> Self {
300        self.form.insert_field("message_thread_id", value);
301        self
302    }
303
304    /// Sets a new parse mode.
305    ///
306    /// # Arguments
307    ///
308    /// `value` - Mode for parsing entities in the media caption.
309    pub fn with_parse_mode(mut self, value: ParseMode) -> Self {
310        self.form.insert_field("parse_mode", value);
311        self.form.remove_field("caption_entities");
312        self
313    }
314
315    /// Sets a new payload.
316    ///
317    /// # Arguments
318    ///
319    /// * `value` - Bot-defined paid media payload;
320    ///   0-128 bytes;
321    ///   This will not be displayed to the user, use it for your internal processes.
322    pub fn with_payload<T>(mut self, value: T) -> Self
323    where
324        T: Into<String>,
325    {
326        self.form.insert_field("payload", value.into());
327        self
328    }
329
330    /// Sets a new value for the `protect_content` flag.
331    ///
332    /// # Arguments
333    ///
334    /// `value` - Whether to protect the contents of the sent message from forwarding and saving.
335    pub fn with_protect_content(mut self, value: bool) -> Self {
336        self.form.insert_field("protect_content", value);
337        self
338    }
339
340    /// Sets new reply parameters.
341    ///
342    /// # Arguments
343    ///
344    /// `value` - Description of the message to reply to.
345    pub fn with_reply_parameters(mut self, value: ReplyParameters) -> Result<Self, ReplyParametersError> {
346        let value = value.serialize()?;
347        self.form.insert_field("reply_parameters", value);
348        Ok(self)
349    }
350
351    /// Sets a new reply markup.
352    ///
353    /// # Arguments
354    ///
355    /// `value` - Additional interface options.
356    pub fn with_reply_markup<T>(mut self, value: T) -> Result<Self, ReplyMarkupError>
357    where
358        T: Into<ReplyMarkup>,
359    {
360        let value = value.into().serialize()?;
361        self.form.insert_field("reply_markup", value);
362        Ok(self)
363    }
364
365    /// Sets a new value for the `show_caption_above_media` flag.
366    ///
367    /// # Arguments
368    ///
369    /// `value` - Whether the caption must be shown above the message media.
370    pub fn with_show_caption_above_media(mut self, value: bool) -> Self {
371        self.form.insert_field("show_caption_above_media", value);
372        self
373    }
374
375    /// Sets a new suggested post parameters.
376    ///
377    /// # Arguments
378    ///
379    /// * `value` - An object containing the parameters of the suggested post to send.
380    ///
381    /// For direct messages chats only.
382    ///
383    /// If the message is sent as a reply to another suggested post, then that suggested post is automatically declined.
384    pub fn with_suggested_post_parameters(
385        mut self,
386        value: &SuggestedPostParameters,
387    ) -> Result<Self, SuggestedPostParametersError> {
388        let value = serde_json::to_string(value).map_err(SuggestedPostParametersError::Serialize)?;
389        self.form.insert_field("suggested_post_parameters", value);
390        Ok(self)
391    }
392}
393
394impl Method for SendPaidMedia {
395    type Response = Message;
396
397    fn into_payload(self) -> Payload {
398        Payload::form("sendPaidMedia", self.form)
399    }
400}