tgbot/types/definitions/
story.rs

1use std::{error::Error, fmt};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Error as JsonError;
5
6use crate::types::{Chat, Float, Integer, LocationAddress, ReactionType};
7
8/// Represents a story.
9#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
10pub struct Story {
11    /// Chat that posted the story.
12    pub chat: Chat,
13    /// Unique identifier of the story in the chat.
14    pub id: Integer,
15}
16
17impl Story {
18    /// Creates a new `Story`.
19    ///
20    /// # Arguments
21    ///
22    /// * `chat` - Chat that posted the story.
23    /// * `id` - Unique identifier of the story in the chat.
24    pub fn new<T>(chat: T, id: Integer) -> Self
25    where
26        T: Into<Chat>,
27    {
28        Self { chat: chat.into(), id }
29    }
30
31    /// Sets a new chat.
32    ///
33    /// # Arguments
34    ///
35    /// * `value` - Chat that posted the story.
36    pub fn with_chat<T>(mut self, value: T) -> Self
37    where
38        T: Into<Chat>,
39    {
40        self.chat = value.into();
41        self
42    }
43
44    /// Sets a new ID.
45    ///
46    /// # Arguments
47    ///
48    /// `value` - Unique identifier of the story in the chat.
49    pub fn with_id(mut self, value: Integer) -> Self {
50        self.id = value;
51        self
52    }
53}
54
55/// Describes a list of clickable areas on a story media.
56pub struct StoryAreas {
57    items: Vec<StoryArea>,
58}
59
60impl StoryAreas {
61    pub(crate) fn serialize(&self) -> Result<String, StoryAreasError> {
62        serde_json::to_string(&self.items).map_err(StoryAreasError::Serialize)
63    }
64}
65
66impl<T> From<T> for StoryAreas
67where
68    T: IntoIterator<Item = StoryArea>,
69{
70    fn from(value: T) -> Self {
71        Self {
72            items: value.into_iter().collect(),
73        }
74    }
75}
76
77/// Represents a story areas error
78#[derive(Debug)]
79pub enum StoryAreasError {
80    /// Can not serialize to JSON
81    Serialize(JsonError),
82}
83
84impl fmt::Display for StoryAreasError {
85    fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
86        match self {
87            Self::Serialize(err) => write!(out, "can not serialize: {err}"),
88        }
89    }
90}
91
92impl Error for StoryAreasError {
93    fn source(&self) -> Option<&(dyn Error + 'static)> {
94        Some(match self {
95            Self::Serialize(err) => err,
96        })
97    }
98}
99
100/// Describes a clickable area on a story media.
101#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
102pub struct StoryArea {
103    /// Type of the area.
104    #[serde(rename = "type")]
105    pub area_type: StoryAreaType,
106    /// Position of the area
107    pub position: StoryAreaPosition,
108}
109
110impl StoryArea {
111    /// Creates a new `StoryArea`.
112    ///
113    /// # Arguments
114    ///
115    /// * `area_type` - Type of the area.
116    /// * `position` - Position of the area.
117    pub fn new<T>(area_type: T, position: StoryAreaPosition) -> Self
118    where
119        T: Into<StoryAreaType>,
120    {
121        Self {
122            area_type: area_type.into(),
123            position,
124        }
125    }
126}
127
128/// Describes the position of a clickable area within a story.
129#[derive(Clone, Debug, Deserialize, derive_more::From, PartialEq, PartialOrd, Serialize)]
130pub struct StoryAreaPosition {
131    /// The radius of the rectangle corner rounding, as a percentage of the media width.
132    pub corner_radius_percentage: Float,
133    /// The height of the area's rectangle, as a percentage of the media height.
134    pub height_percentage: Float,
135    /// The clockwise rotation angle of the rectangle, in degrees; 0-360.
136    pub rotation_angle: Float,
137    /// The width of the area's rectangle, as a percentage of the media width.
138    pub width_percentage: Float,
139    /// The abscissa of the area's center, as a percentage of the media width.
140    pub x_percentage: Float,
141    /// The ordinate of the area's center, as a percentage of the media height.
142    pub y_percentage: Float,
143}
144
145/// Describes the type of a clickable area on a story.
146#[derive(Clone, Debug, Deserialize, derive_more::From, PartialEq, PartialOrd, Serialize)]
147#[serde(tag = "type", rename_all = "snake_case")]
148pub enum StoryAreaType {
149    /// An area pointing to an HTTP or tg:// link.
150    Link(StoryAreaTypeLink),
151    /// An area pointing to a location.
152    Location(StoryAreaTypeLocation),
153    /// An area pointing to a suggested reaction.
154    SuggestedReaction(StoryAreaTypeSuggestedReaction),
155    /// An area pointing to a unique gift.
156    UniqueGift(StoryAreaTypeUniqueGift),
157    /// An area containing weather information.
158    Weather(StoryAreaTypeWeather),
159}
160
161/// Describes a story area pointing to an HTTP or tg:// link.
162///
163/// Currently, a story can have up to 3 link areas.
164#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
165pub struct StoryAreaTypeLink {
166    /// HTTP or tg:// URL to be opened when the area is clicked.
167    pub url: String,
168}
169
170impl StoryAreaTypeLink {
171    /// Creates a new `StoryAreaTypeLink`.
172    ///
173    /// # Arguments
174    ///
175    /// * `url` - HTTP or tg:// URL to be opened when the area is clicked.
176    pub fn new<T>(url: T) -> Self
177    where
178        T: Into<String>,
179    {
180        Self { url: url.into() }
181    }
182}
183
184/// Describes a story area pointing to a location.
185///
186/// Currently, a story can have up to 10 location areas.
187#[serde_with::skip_serializing_none]
188#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
189pub struct StoryAreaTypeLocation {
190    /// Location latitude in degrees.
191    pub latitude: Float,
192    /// Location longitude in degrees.
193    pub longitude: Float,
194    /// Address of the location.
195    pub address: Option<LocationAddress>,
196}
197
198impl StoryAreaTypeLocation {
199    /// Creates a new `StoryAreaTypeLocation`.
200    ///
201    /// # Arguments
202    ///
203    /// * `latitude` - Location latitude in degrees.
204    /// * `longitude` - Location longitude in degrees.
205    pub fn new(latitude: Float, longitude: Float) -> Self {
206        Self {
207            latitude,
208            longitude,
209            address: None,
210        }
211    }
212
213    /// Sets a new address
214    ///
215    /// # Arguments
216    ///
217    /// * `value` - Address of the location.
218    pub fn with_address(mut self, value: LocationAddress) -> Self {
219        self.address = Some(value);
220        self
221    }
222}
223
224/// Describes a story area pointing to a suggested reaction.
225///
226/// Currently, a story can have up to 5 suggested reaction areas.
227#[serde_with::skip_serializing_none]
228#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
229pub struct StoryAreaTypeSuggestedReaction {
230    /// Type of the reaction.
231    pub reaction_type: ReactionType,
232    /// Whether the reaction area has a dark background.
233    pub is_dark: Option<bool>,
234    /// Whether reaction area corner is flipped.
235    pub is_flipped: Option<bool>,
236}
237
238impl StoryAreaTypeSuggestedReaction {
239    /// Creates a new `StoryAreaTypeSuggestedReaction`.
240    ///
241    /// # Arguments
242    ///
243    /// * `reaction_type` - Type of the reaction.
244    pub fn new(reaction_type: ReactionType) -> Self {
245        Self {
246            reaction_type,
247            is_dark: None,
248            is_flipped: None,
249        }
250    }
251
252    /// Sets a new value for the `is_dark` flag.
253    ///
254    /// # Arguments
255    ///
256    /// * `value` - Whether the reaction area has a dark background.
257    pub fn with_is_dark(mut self, value: bool) -> Self {
258        self.is_dark = Some(value);
259        self
260    }
261
262    /// Sets a new value for the `is_flipped` flag.
263    ///
264    /// # Arguments
265    ///
266    /// * `value` - Whether reaction area corner is flipped.
267    pub fn with_is_flipped(mut self, value: bool) -> Self {
268        self.is_flipped = Some(value);
269        self
270    }
271}
272
273/// Describes a story area pointing to a unique gift.
274///
275/// Currently, a story can have at most 1 unique gift area.
276#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
277pub struct StoryAreaTypeUniqueGift {
278    /// Unique name of the gift.
279    pub name: String,
280}
281
282impl StoryAreaTypeUniqueGift {
283    /// Creates a new `StoryAreaTypeUniqueGift`.
284    ///
285    /// # Arguments
286    ///
287    /// * `name` - Unique name of the gift.
288    pub fn new<T>(name: T) -> Self
289    where
290        T: Into<String>,
291    {
292        Self { name: name.into() }
293    }
294}
295
296/// Describes a story area containing weather information.
297///
298/// Currently, a story can have up to 3 weather areas.
299#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
300pub struct StoryAreaTypeWeather {
301    /// A color of the area background in the ARGB format.
302    pub background_color: Integer,
303    /// Emoji representing the weather.
304    pub emoji: String,
305    /// Temperature, in degree Celsius
306    pub temperature: Float,
307}
308
309impl StoryAreaTypeWeather {
310    /// Creates a new `StoryAreaTypeWeather`.
311    ///
312    /// # Arguments
313    ///
314    /// * `background_color` - A color of the area background in the ARGB format.
315    /// * `emoji` - Emoji representing the weather.
316    /// * `temperature` - Temperature, in degree Celsius.
317    pub fn new<T>(background_color: Integer, emoji: T, temperature: Float) -> Self
318    where
319        T: Into<String>,
320    {
321        Self {
322            background_color,
323            emoji: emoji.into(),
324            temperature,
325        }
326    }
327}