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}