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}