twitter_archive/structs/
tweetdeck.rs

1#!/usr/bin/env rust
2
3//! Tweeter archives as of 2023-08-31 have public tweetdeck found under;
4//!
5//!   twitter-<DATE>-<UID>.zip:data/tweetdeck.js
6//!
7//! ## Example file reader
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::tweetdeck;
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/tweetdeck.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.tweetdeck.part0 = ", "", 1);
26//!     let data: Vec<tweetdeck::DeckObject> = serde_json::from_str(&json).expect("Unable to parse");
27//!
28//!     for (index_deck, object_deck) in data.iter().enumerate() {
29//!         /* Do stuff with each Deck */
30//!         println!("Deck index: {index_deck}");
31//!         for (index_column, column) in object_deck.deck.columns.iter().enumerate() {
32//!             /* Do stuff with each Deck's columns */
33//!             println!("  Column index: {index_deck}");
34//!             if let Some(title) = &column.title {
35//!                 println!("  Title: {title}");
36//!             }
37//!
38//!             if let Some(query) = &column.query {
39//!                 println!("  Query: {query}");
40//!             }
41//!
42//!             println!("  Path name: {}", column.pathname);
43//!         }
44//!     }
45//! }
46//! ```
47//!
48//! ## Example `twitter-<DATE>-<UID>.zip:data/tweetdeck.js` content
49//!
50//! ```javascript
51//! window.YTD.tweetdeck.part0 = [
52//!   {
53//!     "deck" : {
54//!       "title" : "Personal",
55//!       "columns" : [
56//!         {
57//!           "pathname" : "/home",
58//!           "title" : "Home"
59//!         },
60//!         {
61//!           "pathname" : "/notifications",
62//!           "title" : "🔔-Notifications"
63//!         },
64//!         {
65//!           "pathname" : "/S0_And_S0",
66//!           "query" : "from:S0_And_S0"
67//!         }
68//!       ]
69//!     }
70//!   },
71//!   {
72//!     "deck" : {
73//!       "title" : "Timelines",
74//!       "columns" : [
75//!         {
76//!           "pathname" : "/S0_And_S0"
77//!         },
78//!         {
79//!           "pathname" : "/S0_And_S0/timelines/1161839635128967168"
80//!         },
81//!         {
82//!           "pathname" : "/S0_And_S0/timelines/1161837773554212864?urtUrl="
83//!         }
84//!       ]
85//!     }
86//!   }
87//! ]
88//! ```
89
90use derive_more::Display;
91use serde::{Deserialize, Serialize};
92
93/// ## Example
94///
95/// ```
96/// use twitter_archive::structs::tweetdeck::DeckObject;
97///
98/// let json = r#"{
99///   "deck": {
100///     "title": "Personal",
101///     "columns": [
102///       {
103///         "pathname": "/home",
104///         "title": "Home"
105///       },
106///       {
107///         "pathname": "/notifications",
108///         "title": "🔔-Notifications"
109///       },
110///       {
111///         "pathname": "/S0_And_S0",
112///         "query": "from:S0_And_S0"
113///       }
114///     ]
115///   }
116/// }"#;
117///
118/// let data: DeckObject = serde_json::from_str(&json).unwrap();
119///
120/// // De-serialized properties
121/// assert_eq!(data.deck.title, "Personal");
122/// assert_eq!(data.deck.columns.len(), 3);
123///
124/// // Re-serialize is equivalent to original data
125/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
126/// ```
127#[derive(Deserialize, Serialize, Debug, Clone, Display)]
128#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
129pub struct DeckObject {
130	/// ## Example JSON data
131	///
132	/// ```json
133	/// {
134	///   "deck": {
135	///     "title": "Personal",
136	///     "columns": [
137	///       {
138	///         "pathname": "/home",
139	///         "title": "Home"
140	///       },
141	///       {
142	///         "pathname": "/notifications",
143	///         "title": "🔔-Notifications"
144	///       },
145	///       {
146	///         "pathname": "/S0_And_S0",
147	///         "query": "from:S0_And_S0"
148	///       }
149	///     ]
150	///   }
151	/// }
152	/// ```
153	pub deck: Deck,
154}
155
156/// ## Example
157///
158/// ```
159/// use twitter_archive::structs::tweetdeck::Deck;
160///
161/// let json = r#"{
162///   "title": "Personal",
163///   "columns": [
164///     {
165///       "pathname": "/home",
166///       "title": "Home"
167///     },
168///     {
169///       "pathname": "/notifications",
170///       "title": "🔔-Notifications"
171///     },
172///     {
173///       "pathname": "/S0_And_S0",
174///       "query": "from:S0_And_S0"
175///     }
176///   ]
177/// }"#;
178///
179/// let data: Deck = serde_json::from_str(&json).unwrap();
180///
181/// // De-serialized properties
182/// assert_eq!(data.title, "Personal");
183/// assert_eq!(data.columns.len(), 3);
184///
185/// // Re-serialize is equivalent to original data
186/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
187/// ```
188#[derive(Deserialize, Serialize, Debug, Clone, Display)]
189#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
190pub struct Deck {
191	/// ## Example JSON data
192	///
193	/// ```json
194	/// { "title": "Personal" }
195	/// ```
196	pub title: String,
197
198	/// ## Example JSON data
199	///
200	/// ```json
201	/// {
202	///   "columns": [
203	///     {
204	///       "pathname": "/home",
205	///       "title": "Home"
206	///     },
207	///     {
208	///       "pathname": "/notifications",
209	///       "title": "🔔-Notifications"
210	///     },
211	///     {
212	///       "pathname": "/S0_And_S0",
213	///       "query": "from:S0_And_S0"
214	///     }
215	///   ]
216	/// }
217	/// ```
218	pub columns: Vec<DeckColumn>,
219}
220
221/// ## Example
222///
223/// ```
224/// use twitter_archive::structs::tweetdeck::DeckColumn;
225///
226/// let json = r#"{
227///   "pathname": "/home",
228///   "title": "Home"
229/// }"#;
230///
231/// let data: DeckColumn = serde_json::from_str(&json).unwrap();
232///
233/// // De-serialized properties
234/// assert_eq!(data.pathname, "/home".to_string());
235/// assert_eq!(data.title, Some("Home".to_string()));
236/// assert_eq!(data.query, None);
237///
238/// // Re-serialize is equivalent to original data
239/// assert_eq!(serde_json::to_string_pretty(&data).unwrap(), json);
240/// ```
241#[derive(Deserialize, Serialize, Debug, Clone, Display)]
242#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
243pub struct DeckColumn {
244	/// URL format
245	///
246	/// - `https://twitter.com/S0_And_S0/status{pathname}`
247	///
248	/// ## Example JSON data
249	///
250	/// ```json
251	/// { "pathname": "/home" }
252	/// ```
253	pub pathname: String,
254
255	/// ## Example JSON data
256	///
257	/// ```json
258	/// { "title": "Home" }
259	/// ```
260	#[serde(skip_serializing_if = "Option::is_none")]
261	pub title: Option<String>,
262
263	/// ## Example JSON data
264	///
265	/// ```json
266	/// { "query": "from:S0_And_S0" }
267	/// ```
268	#[serde(skip_serializing_if = "Option::is_none")]
269	pub query: Option<String>,
270}