twitter_archive/structs/
ad_engagements.rs

1#!/usr/bin/env rust
2
3//! Tweeter archives as of 2023-08-31 have private blocking data found under;
4//!
5//!   twitter-<DATE>-<UID>.zip:data/ad-engagements.js
6//!
7//! ## Example file reader for `twitter-<DATE>-<UID>.zip:data/ad-engagements.js`
8//!
9//! ```no_build
10//! use std::io::Read;
11//! use std::{fs, path};
12//! use zip::read::ZipArchive;
13//!
14//! use twitter_archive::structs::ad_engagements;
15//!
16//! fn main() {
17//!     let input_file = "~/Downloads/twitter-archive.zip";
18//!
19//!     let file_descriptor = fs::File::open(input_file).expect("Unable to read --input-file");
20//!     let mut zip_archive = ZipArchive::new(file_descriptor).unwrap();
21//!     let mut zip_file = zip_archive.by_name("data/ad-engagements.js").unwrap();
22//!     let mut buff = String::new();
23//!     zip_file.read_to_string(&mut buff).unwrap();
24//!
25//!     let json = buff.replacen("window.YTD.ad_engagements.part0 = ", "", 1);
26//!     let data: Vec<ad_engagements::AdObject> = serde_json::from_str(&json).expect("Unable to parse");
27//!
28//!     for (index_ad, object_ad) in data.iter().enumerate() {
29//!         /* Do stuff with each advertisement */
30//!         println!("Advertisement index: {index_ad}");
31//!         let engagements = &object_ad.ad.ads_user_data.ad_engagements.engagements;
32//!         for (index_engagement, object_engagement) in engagements.iter().enumerate() {
33//!             if let Some(promoted_tweet_info) = &object_engagement.impression_attributes.promoted_tweet_info {
34//!                 println!("Promoted Tweet ID: {}", promoted_tweet_info.tweet_id);
35//!                 println!("Promoted Tweet text: {}", promoted_tweet_info.tweet_text);
36//!             }
37//!             println!("Impression time: {}", object_engagement.impression_attributes.impression_time);
38//!         }
39//!     }
40//! }
41//! ```
42//!
43//! ## Example `twitter-<DATE>-<UID>.zip:data/ad-engagements.js` content
44//!
45//! ```javascript
46//! window.YTD.ad_engagements.part0 = [
47//!   {
48//!     "ad" : {
49//!       "adsUserData" : {
50//!         "adEngagements" : {
51//!           "engagements" : [
52//!             {
53//!               "deviceInfo" : {
54//!                 "osType" : "Desktop"
55//!               },
56//!               "displayLocation" : "TweetConversation",
57//!               "promotedTweetInfo" : {
58//!                 "tweetId" : "1111111111111111111",
59//!                 "tweetText" : "Click bate",
60//!                 "urls" : [ ],
61//!                 "mediaUrls" : [
62//!                   "https://t.co/AHAAAAAAAA"
63//!                 ]
64//!               },
65//!               "advertiserInfo" : {
66//!                 "advertiserName" : "EXAMPLE",
67//!                 "screenName" : "@EXAMPLE"
68//!               },
69//!               "matchedTargetingCriteria" : [
70//!                 {
71//!                   "targetingType" : "Follower look-alikes",
72//!                   "targetingValue" : "@EXAMPLE"
73//!                 }
74//!               ],
75//!               "impressionTime" : "2023-06-05 17:00:52"
76//!             }
77//!           ]
78//!         }
79//!       }
80//!     }
81//!   }
82//! ]
83//! ```
84
85use chrono::{DateTime, Utc};
86use derive_more::Display;
87use serde::{Deserialize, Serialize};
88
89use crate::convert;
90use crate::structs::ad;
91
92/// ## Example
93///
94/// ```
95/// use chrono::{DateTime, NaiveDateTime, Utc};
96///
97/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
98///
99/// use twitter_archive::structs::ad_engagements::AdObject;
100///
101/// let impression_time_string = "2023-06-05 17:00:52";
102/// let impression_time_native_time = NaiveDateTime::parse_from_str(&impression_time_string, FORMAT).unwrap();
103/// let impression_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(impression_time_native_time, Utc);
104///
105/// let engagement_time_string = "2023-06-05 17:00:52";
106/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
107/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
108///
109/// let json = format!(r#"{{
110///   "ad": {{
111///     "adsUserData": {{
112///       "adEngagements": {{
113///         "engagements": [
114///           {{
115///             "impressionAttributes": {{
116///               "deviceInfo": {{
117///                 "osType": "Desktop"
118///               }},
119///               "displayLocation": "TweetConversation",
120///               "promotedTweetInfo": {{
121///                 "tweetId": "1111111111111111111",
122///                 "tweetText": "Click bate",
123///                 "urls": [],
124///                 "mediaUrls": [
125///                   "https://t.co/AHAAAAAAAA"
126///                 ]
127///               }},
128///               "advertiserInfo": {{
129///                 "advertiserName": "EXAMPLE",
130///                 "screenName": "@EXAMPLE"
131///               }},
132///               "matchedTargetingCriteria": [
133///                 {{
134///                   "targetingType": "Follower look-alikes",
135///                   "targetingValue": "@EXAMPLE"
136///                 }}
137///               ],
138///               "impressionTime": "{impression_time_string}"
139///             }},
140///             "engagementAttributes": [
141///               {{
142///                 "engagementTime": "{engagement_time_string}",
143///                 "engagementType": "ChargeableImpression"
144///               }},
145///               {{
146///                 "engagementTime": "{engagement_time_string}",
147///                 "engagementType": "Mute"
148///               }}
149///             ]
150///           }}
151///         ]
152///       }}
153///     }}
154///   }}
155/// }}"#);
156///
157/// let data: AdObject = serde_json::from_str(&json).unwrap();
158///
159/// // De-serialized properties
160/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements.len(), 1);
161///
162/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.device_info.os_type, "Desktop");
163///
164/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.display_location, "TweetConversation");
165///
166/// if let Some(promoted_tweet_info) = &data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.promoted_tweet_info {
167///     assert_eq!(promoted_tweet_info.tweet_id, "1111111111111111111");
168///     assert_eq!(promoted_tweet_info.tweet_text, "Click bate");
169///     assert_eq!(promoted_tweet_info.urls.len(), 0);
170///     assert_eq!(promoted_tweet_info.media_urls.len(), 1);
171///     assert_eq!(promoted_tweet_info.media_urls[0], "https://t.co/AHAAAAAAAA");
172/// }
173///
174/// if let Some(advertiser_name) = &data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.advertiser_info.advertiser_name {
175///     assert_eq!(advertiser_name, "EXAMPLE");
176/// }
177/// if let Some(screen_name) = &data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.advertiser_info.screen_name {
178///     assert_eq!(screen_name, "@EXAMPLE");
179/// }
180///
181/// if let Some(matched_targeting_criteria) = &data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.matched_targeting_criteria {
182///     assert_eq!(matched_targeting_criteria.len(), 1);
183///     assert_eq!(matched_targeting_criteria[0].targeting_type, "Follower look-alikes");
184///     if let Some(targeting_value) = &matched_targeting_criteria[0].targeting_value {
185///         assert_eq!(targeting_value, "@EXAMPLE");
186///     }
187/// }
188///
189/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].impression_attributes.impression_time, impression_time_date_time);
190///
191/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].engagement_attributes[0].engagement_time, engagement_time_date_time);
192/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].engagement_attributes[0].engagement_type, "ChargeableImpression");
193/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].engagement_attributes[1].engagement_time, engagement_time_date_time);
194/// assert_eq!(data.ad.ads_user_data.ad_engagements.engagements[0].engagement_attributes[1].engagement_type, "Mute");
195///
196/// // Re-serialize is equivalent to original data without pretty printing
197/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
198/// ```
199#[derive(Deserialize, Serialize, Debug, Clone, Display)]
200#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
201#[serde(rename_all = "camelCase")]
202pub struct AdObject {
203	/// ## Example JSON data
204	///
205	/// ```json
206	/// {
207	///   "ad": {
208	///     "adsUserData": {
209	///       "adEngagements": {
210	///         "engagements": [
211	///           {
212	///             "impressionAttributes": {
213	///               "deviceInfo": {
214	///                 "osType": "Desktop"
215	///               },
216	///               "displayLocation": "TweetConversation",
217	///               "promotedTweetInfo": {
218	///                 "tweetId": "1111111111111111111",
219	///                 "tweetText": "Click bate",
220	///                 "urls": [],
221	///                 "mediaUrls": [
222	///                   "https://t.co/AHAAAAAAAA"
223	///                 ]
224	///               },
225	///               "advertiserInfo": {
226	///                 "advertiserName": "EXAMPLE",
227	///                 "screenName": "@EXAMPLE"
228	///               },
229	///               "matchedTargetingCriteria": [
230	///                 {
231	///                   "targetingType": "Follower look-alikes",
232	///                   "targetingValue": "@EXAMPLE"
233	///                 }
234	///               ],
235	///               "impressionTime": "2023-06-05 17:00:52"
236	///             },
237	///             "engagementAttributes": [
238	///               {
239	///                 "engagementTime": "2023-06-05 17:00:52",
240	///                 "engagementType": "ChargeableImpression"
241	///               },
242	///               {
243	///                 "engagementTime": "2023-06-05 17:00:52",
244	///                 "engagementType": "Mute"
245	///               }
246	///             ]
247	///           }
248	///         ]
249	///       }
250	///     }
251	///   }
252	/// }
253	/// ```
254	pub ad: Ad,
255}
256
257/// ## Example
258///
259/// ```
260/// use chrono::{DateTime, NaiveDateTime, Utc};
261///
262/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
263///
264/// use twitter_archive::structs::ad_engagements::Ad;
265///
266/// let impression_time_string = "2023-06-05 17:00:52";
267/// let impression_time_native_time = NaiveDateTime::parse_from_str(&impression_time_string, FORMAT).unwrap();
268/// let impression_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(impression_time_native_time, Utc);
269///
270/// let engagement_time_string = "2023-06-05 17:00:52";
271/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
272/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
273///
274/// let json = format!(r#"{{
275///   "adsUserData": {{
276///     "adEngagements": {{
277///       "engagements": [
278///         {{
279///           "impressionAttributes": {{
280///             "deviceInfo": {{
281///               "osType": "Desktop"
282///             }},
283///             "displayLocation": "TweetConversation",
284///             "promotedTweetInfo": {{
285///               "tweetId": "1111111111111111111",
286///               "tweetText": "Click bate",
287///               "urls": [],
288///               "mediaUrls": [
289///                 "https://t.co/AHAAAAAAAA"
290///               ]
291///             }},
292///             "advertiserInfo": {{
293///               "advertiserName": "EXAMPLE",
294///               "screenName": "@EXAMPLE"
295///             }},
296///             "matchedTargetingCriteria": [
297///               {{
298///                 "targetingType": "Follower look-alikes",
299///                 "targetingValue": "@EXAMPLE"
300///               }}
301///             ],
302///             "impressionTime": "{impression_time_string}"
303///           }},
304///           "engagementAttributes": [
305///             {{
306///               "engagementTime": "{engagement_time_string}",
307///               "engagementType": "ChargeableImpression"
308///             }},
309///             {{
310///               "engagementTime": "{engagement_time_string}",
311///               "engagementType": "Mute"
312///             }}
313///           ]
314///         }}
315///       ]
316///     }}
317///   }}
318/// }}"#);
319///
320/// let data: Ad = serde_json::from_str(&json).unwrap();
321///
322/// // De-serialized properties
323/// assert_eq!(data.ads_user_data.ad_engagements.engagements.len(), 1);
324///
325/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].impression_attributes.device_info.os_type, "Desktop");
326///
327/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].impression_attributes.display_location, "TweetConversation");
328///
329/// if let Some(promoted_tweet_info) = &data.ads_user_data.ad_engagements.engagements[0].impression_attributes.promoted_tweet_info {
330///     assert_eq!(promoted_tweet_info.tweet_id, "1111111111111111111");
331///     assert_eq!(promoted_tweet_info.tweet_text, "Click bate");
332///     assert_eq!(promoted_tweet_info.urls.len(), 0);
333///     assert_eq!(promoted_tweet_info.media_urls.len(), 1);
334///     assert_eq!(promoted_tweet_info.media_urls[0], "https://t.co/AHAAAAAAAA");
335/// }
336///
337/// if let Some(advertiser_name) = &data.ads_user_data.ad_engagements.engagements[0].impression_attributes.advertiser_info.advertiser_name {
338///     assert_eq!(advertiser_name, "EXAMPLE");
339/// }
340/// if let Some(screen_name) = &data.ads_user_data.ad_engagements.engagements[0].impression_attributes.advertiser_info.screen_name {
341///     assert_eq!(screen_name, "@EXAMPLE");
342/// }
343///
344/// if let Some(matched_targeting_criteria) = &data.ads_user_data.ad_engagements.engagements[0].impression_attributes.matched_targeting_criteria {
345///     assert_eq!(matched_targeting_criteria.len(), 1);
346///     assert_eq!(matched_targeting_criteria[0].targeting_type, "Follower look-alikes");
347///     if let Some(targeting_value) = &matched_targeting_criteria[0].targeting_value {
348///         assert_eq!(targeting_value, "@EXAMPLE");
349///     }
350/// }
351///
352/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].impression_attributes.impression_time, impression_time_date_time);
353///
354/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].engagement_attributes[0].engagement_time, engagement_time_date_time);
355/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].engagement_attributes[0].engagement_type, "ChargeableImpression");
356/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].engagement_attributes[1].engagement_time, engagement_time_date_time);
357/// assert_eq!(data.ads_user_data.ad_engagements.engagements[0].engagement_attributes[1].engagement_type, "Mute");
358///
359/// // Re-serialize is equivalent to original data without pretty printing
360/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
361/// ```
362#[derive(Deserialize, Serialize, Debug, Clone, Display)]
363#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
364#[serde(rename_all = "camelCase")]
365pub struct Ad {
366	/// ## Example JSON data
367	///
368	/// ```json
369	/// {
370	///   "adsUserData": {
371	///     "adEngagements": {
372	///       "engagements": [
373	///         {
374	///           "impressionAttributes": {
375	///             "deviceInfo": {
376	///               "osType": "Desktop"
377	///             },
378	///             "displayLocation": "TweetConversation",
379	///             "promotedTweetInfo": {
380	///               "tweetId": "1111111111111111111",
381	///               "tweetText": "Click bate",
382	///               "urls": [],
383	///               "mediaUrls": [
384	///                 "https://t.co/AHAAAAAAAA"
385	///               ]
386	///             },
387	///             "advertiserInfo": {
388	///               "advertiserName": "EXAMPLE",
389	///               "screenName": "@EXAMPLE"
390	///             },
391	///             "matchedTargetingCriteria": [
392	///               {
393	///                 "targetingType": "Follower look-alikes",
394	///                 "targetingValue": "@EXAMPLE"
395	///               }
396	///             ],
397	///             "impressionTime": "2023-06-05 17:00:52"
398	///           },
399	///           "engagementAttributes": [
400	///             {
401	///               "engagementTime": "2023-06-05 17:00:52",
402	///               "engagementType": "ChargeableImpression"
403	///             },
404	///             {
405	///               "engagementTime": "2023-06-05 17:00:52",
406	///               "engagementType": "Mute"
407	///             }
408	///           ]
409	///         }
410	///       ]
411	///     }
412	///   }
413	/// }
414	/// ```
415	pub ads_user_data: AdsUserData,
416}
417
418/// ## Example
419///
420/// ```
421/// use chrono::{DateTime, NaiveDateTime, Utc};
422///
423/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
424///
425/// use twitter_archive::structs::ad_engagements::AdsUserData;
426///
427/// let impression_time_string = "2023-06-05 17:00:52";
428/// let impression_time_native_time = NaiveDateTime::parse_from_str(&impression_time_string, FORMAT).unwrap();
429/// let impression_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(impression_time_native_time, Utc);
430///
431/// let engagement_time_string = "2023-06-05 17:00:52";
432/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
433/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
434///
435/// let json = format!(r#"{{
436///   "adEngagements": {{
437///     "engagements": [
438///       {{
439///         "impressionAttributes": {{
440///           "deviceInfo": {{
441///             "osType": "Desktop"
442///           }},
443///           "displayLocation": "TweetConversation",
444///           "promotedTweetInfo": {{
445///             "tweetId": "1111111111111111111",
446///             "tweetText": "Click bate",
447///             "urls": [],
448///             "mediaUrls": [
449///               "https://t.co/AHAAAAAAAA"
450///             ]
451///           }},
452///           "advertiserInfo": {{
453///             "advertiserName": "EXAMPLE",
454///             "screenName": "@EXAMPLE"
455///           }},
456///           "matchedTargetingCriteria": [
457///             {{
458///               "targetingType": "Follower look-alikes",
459///               "targetingValue": "@EXAMPLE"
460///             }}
461///           ],
462///           "impressionTime": "{impression_time_string}"
463///         }},
464///         "engagementAttributes": [
465///           {{
466///             "engagementTime": "{engagement_time_string}",
467///             "engagementType": "ChargeableImpression"
468///           }},
469///           {{
470///             "engagementTime": "{engagement_time_string}",
471///             "engagementType": "Mute"
472///           }}
473///         ]
474///       }}
475///     ]
476///   }}
477/// }}"#);
478///
479/// let data: AdsUserData = serde_json::from_str(&json).unwrap();
480///
481/// // De-serialized properties
482/// assert_eq!(data.ad_engagements.engagements.len(), 1);
483///
484/// assert_eq!(data.ad_engagements.engagements[0].impression_attributes.device_info.os_type, "Desktop");
485///
486/// assert_eq!(data.ad_engagements.engagements[0].impression_attributes.display_location, "TweetConversation");
487///
488/// if let Some(promoted_tweet_info) = &data.ad_engagements.engagements[0].impression_attributes.promoted_tweet_info {
489///     assert_eq!(promoted_tweet_info.tweet_id, "1111111111111111111");
490///     assert_eq!(promoted_tweet_info.tweet_text, "Click bate");
491///     assert_eq!(promoted_tweet_info.urls.len(), 0);
492///     assert_eq!(promoted_tweet_info.media_urls.len(), 1);
493///     assert_eq!(promoted_tweet_info.media_urls[0], "https://t.co/AHAAAAAAAA");
494/// }
495///
496/// if let Some(advertiser_name) = &data.ad_engagements.engagements[0].impression_attributes.advertiser_info.advertiser_name {
497///     assert_eq!(advertiser_name, "EXAMPLE");
498/// }
499/// if let Some(screen_name) = &data.ad_engagements.engagements[0].impression_attributes.advertiser_info.screen_name {
500///     assert_eq!(screen_name, "@EXAMPLE");
501/// }
502///
503/// if let Some(matched_targeting_criteria) = &data.ad_engagements.engagements[0].impression_attributes.matched_targeting_criteria {
504///     assert_eq!(matched_targeting_criteria.len(), 1);
505///     assert_eq!(matched_targeting_criteria[0].targeting_type, "Follower look-alikes");
506///     if let Some(targeting_value) = &matched_targeting_criteria[0].targeting_value {
507///         assert_eq!(targeting_value, "@EXAMPLE");
508///     }
509/// }
510///
511/// assert_eq!(data.ad_engagements.engagements[0].impression_attributes.impression_time, impression_time_date_time);
512///
513/// assert_eq!(data.ad_engagements.engagements[0].engagement_attributes[0].engagement_time, engagement_time_date_time);
514/// assert_eq!(data.ad_engagements.engagements[0].engagement_attributes[0].engagement_type, "ChargeableImpression");
515/// assert_eq!(data.ad_engagements.engagements[0].engagement_attributes[1].engagement_time, engagement_time_date_time);
516/// assert_eq!(data.ad_engagements.engagements[0].engagement_attributes[1].engagement_type, "Mute");
517///
518/// // Re-serialize is equivalent to original data without pretty printing
519/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
520/// ```
521#[derive(Deserialize, Serialize, Debug, Clone, Display)]
522#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
523#[serde(rename_all = "camelCase")]
524pub struct AdsUserData {
525	/// ## Example JSON data
526	///
527	/// ```json
528	/// {
529	///   "adEngagements": {
530	///     "engagements": [
531	///       {
532	///         "impressionAttributes": {
533	///           "deviceInfo": {
534	///             "osType": "Desktop"
535	///           },
536	///           "displayLocation": "TweetConversation",
537	///           "promotedTweetInfo": {
538	///             "tweetId": "1111111111111111111",
539	///             "tweetText": "Click bate",
540	///             "urls": [],
541	///             "mediaUrls": [
542	///               "https://t.co/AHAAAAAAAA"
543	///             ]
544	///           },
545	///           "advertiserInfo": {
546	///             "advertiserName": "EXAMPLE",
547	///             "screenName": "@EXAMPLE"
548	///           },
549	///           "matchedTargetingCriteria": [
550	///             {
551	///               "targetingType": "Follower look-alikes",
552	///               "targetingValue": "@EXAMPLE"
553	///             }
554	///           ],
555	///           "impressionTime": "2023-06-05 17:00:52"
556	///         },
557	///         "engagementAttributes": [
558	///           {
559	///             "engagementTime": "2023-06-05 17:00:52",
560	///             "engagementType": "ChargeableImpression"
561	///           },
562	///           {
563	///             "engagementTime": "2023-06-05 17:00:52",
564	///             "engagementType": "Mute"
565	///           }
566	///         ]
567	///       }
568	///     ]
569	///   }
570	/// }
571	/// ```
572	pub ad_engagements: AdEngagements,
573}
574
575/// ## Example
576///
577/// ```
578/// use chrono::{DateTime, NaiveDateTime, Utc};
579///
580/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
581///
582/// use twitter_archive::structs::ad_engagements::AdEngagements;
583///
584/// let impression_time_string = "2023-06-05 17:00:52";
585/// let impression_time_native_time = NaiveDateTime::parse_from_str(&impression_time_string, FORMAT).unwrap();
586/// let impression_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(impression_time_native_time, Utc);
587///
588/// let engagement_time_string = "2023-06-05 17:00:52";
589/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
590/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
591///
592/// let json = format!(r#"{{
593///   "engagements": [
594///     {{
595///       "impressionAttributes": {{
596///         "deviceInfo": {{
597///           "osType": "Desktop"
598///         }},
599///         "displayLocation": "TweetConversation",
600///         "promotedTweetInfo": {{
601///           "tweetId": "1111111111111111111",
602///           "tweetText": "Click bate",
603///           "urls": [],
604///           "mediaUrls": [
605///             "https://t.co/AHAAAAAAAA"
606///           ]
607///         }},
608///         "advertiserInfo": {{
609///           "advertiserName": "EXAMPLE",
610///           "screenName": "@EXAMPLE"
611///         }},
612///         "matchedTargetingCriteria": [
613///           {{
614///             "targetingType": "Follower look-alikes",
615///             "targetingValue": "@EXAMPLE"
616///           }}
617///         ],
618///         "impressionTime": "{impression_time_string}"
619///       }},
620///       "engagementAttributes": [
621///         {{
622///           "engagementTime": "{engagement_time_string}",
623///           "engagementType": "ChargeableImpression"
624///         }},
625///         {{
626///           "engagementTime": "{engagement_time_string}",
627///           "engagementType": "Mute"
628///         }}
629///       ]
630///     }}
631///   ]
632/// }}"#);
633///
634/// let data: AdEngagements = serde_json::from_str(&json).unwrap();
635///
636/// // De-serialized properties
637/// assert_eq!(data.engagements.len(), 1);
638///
639/// assert_eq!(data.engagements[0].impression_attributes.device_info.os_type, "Desktop");
640///
641/// assert_eq!(data.engagements[0].impression_attributes.display_location, "TweetConversation");
642///
643/// if let Some(promoted_tweet_info) = &data.engagements[0].impression_attributes.promoted_tweet_info {
644///     assert_eq!(promoted_tweet_info.tweet_id, "1111111111111111111");
645///     assert_eq!(promoted_tweet_info.tweet_text, "Click bate");
646///     assert_eq!(promoted_tweet_info.urls.len(), 0);
647///     assert_eq!(promoted_tweet_info.media_urls.len(), 1);
648///     assert_eq!(promoted_tweet_info.media_urls[0], "https://t.co/AHAAAAAAAA");
649/// }
650///
651/// if let Some(advertiser_name) = &data.engagements[0].impression_attributes.advertiser_info.advertiser_name {
652///     assert_eq!(advertiser_name, "EXAMPLE");
653/// }
654/// if let Some(screen_name) = &data.engagements[0].impression_attributes.advertiser_info.screen_name {
655///     assert_eq!(screen_name, "@EXAMPLE");
656/// }
657///
658/// if let Some(matched_targeting_criteria) = &data.engagements[0].impression_attributes.matched_targeting_criteria {
659///     assert_eq!(matched_targeting_criteria.len(), 1);
660///     assert_eq!(matched_targeting_criteria[0].targeting_type, "Follower look-alikes");
661///     if let Some(targeting_value) = &matched_targeting_criteria[0].targeting_value {
662///         assert_eq!(targeting_value, "@EXAMPLE");
663///     }
664/// }
665///
666/// assert_eq!(data.engagements[0].impression_attributes.impression_time, impression_time_date_time);
667///
668/// assert_eq!(data.engagements[0].engagement_attributes[0].engagement_time, engagement_time_date_time);
669/// assert_eq!(data.engagements[0].engagement_attributes[0].engagement_type, "ChargeableImpression");
670/// assert_eq!(data.engagements[0].engagement_attributes[1].engagement_time, engagement_time_date_time);
671/// assert_eq!(data.engagements[0].engagement_attributes[1].engagement_type, "Mute");
672///
673/// // Re-serialize is equivalent to original data without pretty printing
674/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
675/// ```
676#[derive(Deserialize, Serialize, Debug, Clone, Display)]
677#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
678pub struct AdEngagements {
679	/// ## Example JSON data
680	///
681	/// ```json
682	/// {
683	///   "engagements": [
684	///     {
685	///       "impressionAttributes": {
686	///         "deviceInfo": {
687	///           "osType": "Desktop"
688	///         },
689	///         "displayLocation": "TweetConversation",
690	///         "promotedTweetInfo": {
691	///           "tweetId": "1111111111111111111",
692	///           "tweetText": "Click bate",
693	///           "urls": [],
694	///           "mediaUrls": [
695	///             "https://t.co/AHAAAAAAAA"
696	///           ]
697	///         },
698	///         "advertiserInfo": {
699	///           "advertiserName": "EXAMPLE",
700	///           "screenName": "@EXAMPLE"
701	///         },
702	///         "matchedTargetingCriteria": [
703	///           {
704	///             "targetingType": "Follower look-alikes",
705	///             "targetingValue": "@EXAMPLE"
706	///           }
707	///         ],
708	///         "impressionTime": "2023-06-05 17:00:52"
709	///       },
710	///       "engagementAttributes": [
711	///         {
712	///           "engagementTime": "2023-06-05 17:00:52",
713	///           "engagementType": "ChargeableImpression"
714	///         },
715	///         {
716	///           "engagementTime": "2023-06-05 17:00:52",
717	///           "engagementType": "Mute"
718	///         }
719	///       ]
720	///     }
721	///   ]
722	/// }
723	/// ```
724	pub engagements: Vec<Engagement>,
725}
726
727/// ## Example
728///
729/// ```
730/// use chrono::{DateTime, NaiveDateTime, Utc};
731///
732/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
733///
734/// use twitter_archive::structs::ad_engagements::Engagement;
735///
736/// let impression_time_string = "2023-06-05 17:00:52";
737/// let impression_time_native_time = NaiveDateTime::parse_from_str(&impression_time_string, FORMAT).unwrap();
738/// let impression_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(impression_time_native_time, Utc);
739///
740/// let engagement_time_string = "2023-06-05 17:00:52";
741/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
742/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
743///
744/// let json = format!(r#"{{
745///   "impressionAttributes": {{
746///     "deviceInfo": {{
747///       "osType": "Desktop"
748///     }},
749///     "displayLocation": "TweetConversation",
750///     "promotedTweetInfo": {{
751///       "tweetId": "1111111111111111111",
752///       "tweetText": "Click bate",
753///       "urls": [],
754///       "mediaUrls": [
755///         "https://t.co/AHAAAAAAAA"
756///       ]
757///     }},
758///     "advertiserInfo": {{
759///       "advertiserName": "EXAMPLE",
760///       "screenName": "@EXAMPLE"
761///     }},
762///     "matchedTargetingCriteria": [
763///       {{
764///         "targetingType": "Follower look-alikes",
765///         "targetingValue": "@EXAMPLE"
766///       }}
767///     ],
768///     "impressionTime": "{impression_time_string}"
769///   }},
770///   "engagementAttributes": [
771///     {{
772///       "engagementTime": "{engagement_time_string}",
773///       "engagementType": "ChargeableImpression"
774///     }},
775///     {{
776///       "engagementTime": "{engagement_time_string}",
777///       "engagementType": "Mute"
778///     }}
779///   ]
780/// }}"#);
781///
782/// let data: Engagement = serde_json::from_str(&json).unwrap();
783///
784/// // De-serialized properties
785/// assert_eq!(data.impression_attributes.device_info.os_type, "Desktop");
786///
787/// assert_eq!(data.impression_attributes.display_location, "TweetConversation");
788///
789/// if let Some(promoted_tweet_info) = &data.impression_attributes.promoted_tweet_info {
790///     assert_eq!(promoted_tweet_info.tweet_id, "1111111111111111111");
791///     assert_eq!(promoted_tweet_info.tweet_text, "Click bate");
792///     assert_eq!(promoted_tweet_info.urls.len(), 0);
793///     assert_eq!(promoted_tweet_info.media_urls.len(), 1);
794///     assert_eq!(promoted_tweet_info.media_urls[0], "https://t.co/AHAAAAAAAA");
795/// }
796///
797/// if let Some(advertiser_name) = &data.impression_attributes.advertiser_info.advertiser_name {
798///     assert_eq!(advertiser_name, "EXAMPLE");
799/// }
800/// if let Some(screen_name) = &data.impression_attributes.advertiser_info.screen_name {
801///     assert_eq!(screen_name, "@EXAMPLE");
802/// }
803///
804/// if let Some(matched_targeting_criteria) = &data.impression_attributes.matched_targeting_criteria {
805///     assert_eq!(matched_targeting_criteria.len(), 1);
806///     assert_eq!(matched_targeting_criteria[0].targeting_type, "Follower look-alikes");
807///     if let Some(targeting_value) = &matched_targeting_criteria[0].targeting_value {
808///         assert_eq!(targeting_value, "@EXAMPLE");
809///     }
810/// }
811///
812/// assert_eq!(data.impression_attributes.impression_time, impression_time_date_time);
813///
814/// assert_eq!(data.engagement_attributes[0].engagement_time, engagement_time_date_time);
815/// assert_eq!(data.engagement_attributes[0].engagement_type, "ChargeableImpression");
816/// assert_eq!(data.engagement_attributes[1].engagement_time, engagement_time_date_time);
817/// assert_eq!(data.engagement_attributes[1].engagement_type, "Mute");
818///
819/// // Re-serialize is equivalent to original data without pretty printing
820/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
821/// ```
822#[derive(Deserialize, Serialize, Debug, Clone, Display)]
823#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
824#[serde(rename_all = "camelCase")]
825pub struct Engagement {
826	/// ## Example JSON data
827	///
828	/// ```json
829	/// {
830	///   "impressionAttributes": {
831	///     "deviceInfo": {
832	///       "osType": "Desktop"
833	///     },
834	///     "displayLocation": "TweetConversation",
835	///     "promotedTweetInfo": {
836	///       "tweetId": "1111111111111111111",
837	///       "tweetText": "Click bate",
838	///       "urls": [],
839	///       "mediaUrls": [
840	///         "https://t.co/AHAAAAAAAA"
841	///       ]
842	///     },
843	///     "advertiserInfo": {
844	///       "advertiserName": "EXAMPLE",
845	///       "screenName": "@EXAMPLE"
846	///     },
847	///     "matchedTargetingCriteria": [
848	///       {
849	///         "targetingType": "Follower look-alikes",
850	///         "targetingValue": "@EXAMPLE"
851	///       }
852	///     ],
853	///     "impressionTime": "2023-06-05 17:00:52"
854	///   }
855	/// }
856	/// ```
857	pub impression_attributes: ad::Impression,
858
859	/// ## Example JSON data
860	///
861	/// ```json
862	/// {
863	///   "engagementAttributes": [
864	///     {
865	///       "engagementTime": "2023-06-05 17:00:52",
866	///       "engagementType": "ChargeableImpression"
867	///     },
868	///     {
869	///       "engagementTime": "2023-06-05 17:00:52",
870	///       "engagementType": "Mute"
871	///     }
872	///   ]
873	/// }
874	/// ```
875	pub engagement_attributes: Vec<EngagementAttributes>,
876}
877
878/// ## Example
879///
880/// ```
881/// use chrono::{DateTime, NaiveDateTime, Utc};
882///
883/// use twitter_archive::convert::date_year_month_day_hour_minute_second::FORMAT;
884///
885/// use twitter_archive::structs::ad_engagements::EngagementAttributes;
886///
887/// let engagement_time_string = "2023-06-05 17:00:52";
888/// let engagement_time_native_time = NaiveDateTime::parse_from_str(&engagement_time_string, FORMAT).unwrap();
889/// let engagement_time_date_time = DateTime::<Utc>::from_naive_utc_and_offset(engagement_time_native_time, Utc);
890///
891/// let json = format!(r#"{{
892///   "engagementTime": "{engagement_time_string}",
893///   "engagementType": "ChargeableImpression"
894/// }}"#);
895///
896/// let data: EngagementAttributes = serde_json::from_str(&json).unwrap();
897///
898/// // De-serialized properties
899/// assert_eq!(data.engagement_time, engagement_time_date_time);
900/// assert_eq!(data.engagement_type, "ChargeableImpression");
901///
902/// // Re-serialize is equivalent to original data without pretty printing
903/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
904/// ```
905#[derive(Deserialize, Serialize, Debug, Clone, Display)]
906#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
907#[serde(rename_all = "camelCase")]
908pub struct EngagementAttributes {
909	/// ## Example JSON data
910	///
911	/// ```json
912	/// { "engagementTime": "{engagement_time_string}" }
913	/// ```
914	#[serde(with = "convert::date_year_month_day_hour_minute_second")]
915	pub engagement_time: DateTime<Utc>,
916
917	/// ## Example JSON data
918	///
919	/// ```json
920	/// { "engagementType": "ChargeableImpression" }
921	/// ```
922	pub engagement_type: String,
923}