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}