twitter_archive/structs/connected_application.rs
1#!/usr/bin/env rust
2
3//! Tweeter archives as of 2023-08-31 have public connected_application found under;
4//!
5//! twitter-<DATE>-<UID>.zip:data/connected-application.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::connected_application;
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/connected-application.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.connected_application.part0 = ", "", 1);
26//! let data: Vec<connected_application::ConnectedApplicationObject> = serde_json::from_str(&json).expect("Unable to parse");
27//!
28//! for (index, object) in data.iter().enumerate() {
29//! /* Do stuff with each deleted Tweet */
30//! println!("Connected application index: {index}");
31//! println!("Organization name: {}", object.connected_application.organization.name);
32//! println!("Organization URL: {}", object.connected_application.organization.url);
33//! println!("Organization privacy policy: {}", object.connected_application.organization.privacy_policy_url);
34//! println!("Organization terms and conditions: {}", object.connected_application.organization.terms_and_conditions_url);
35//! println!("Description: {}", object.connected_application.description);
36//! println!("Permissions: {:?}", object.connected_application.permissions);
37//! println!("Approved at: {}", object.connected_application.approved_at);
38//! println!("ID: {}", object.connected_application.id);
39//! }
40//! }
41//! ```
42//!
43//! ## Example `twitter-<DATE>-<UID>.zip:data/connected-application.js` content
44//!
45//! ```javascript
46//! window.YTD.connected_application.part0 = [
47//! {
48//! "connectedApplication" : {
49//! "organization" : {
50//! "name" : "Medium",
51//! "url" : "https://medium.com",
52//! "privacyPolicyUrl" : "https://medium.com/policy/medium-privacy-policy-f03bf92035c9",
53//! "termsAndConditionsUrl" : "https://medium.com/policy/medium-terms-of-service-9db0094a1e0f"
54//! },
55//! "name" : "Medium",
56//! "description" : "Evolving publishing",
57//! "permissions" : [
58//! "read",
59//! "write",
60//! "emailaddress"
61//! ],
62//! "approvedAt" : "2020-01-20T21:42:09.068Z",
63//! "id" : "1111111"
64//! }
65//! }
66//! ]
67//! ```
68
69use chrono::{DateTime, Utc};
70use derive_more::Display;
71use serde::{Deserialize, Serialize};
72
73use crate::convert;
74
75/// ## Example
76///
77/// ```
78/// use chrono::{DateTime, NaiveDateTime, Utc};
79///
80/// use twitter_archive::convert::date_time_iso_8601::FORMAT;
81///
82/// use twitter_archive::structs::connected_application::ConnectedApplicationObject;
83///
84/// let approved_at_string = "2020-01-20T21:42:09.068Z";
85/// let approved_at_native_time = NaiveDateTime::parse_from_str(&approved_at_string, FORMAT).unwrap();
86/// let approved_at_date_time = DateTime::<Utc>::from_naive_utc_and_offset(approved_at_native_time, Utc);
87///
88/// let json = format!(r#"{{
89/// "connectedApplication" : {{
90/// "organization" : {{
91/// "name" : "Example",
92/// "url" : "https://example.com",
93/// "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy",
94/// "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service"
95/// }},
96/// "name" : "Example",
97/// "description" : "Example-description",
98/// "permissions" : [
99/// "read",
100/// "write",
101/// "emailaddress"
102/// ],
103/// "approvedAt" : "{approved_at_string}",
104/// "id" : "1111111"
105/// }}
106/// }}"#);
107///
108/// let data: ConnectedApplicationObject = serde_json::from_str(&json).unwrap();
109///
110/// // De-serialized properties
111/// assert_eq!(data.connected_application.organization.name, "Example");
112/// assert_eq!(data.connected_application.organization.url, "https://example.com");
113/// assert_eq!(data.connected_application.organization.privacy_policy_url, "https://example.com/policy/example-privacy-policy");
114/// assert_eq!(data.connected_application.organization.terms_and_conditions_url, "https://example.com/policy/medium-terms-of-service");
115///
116/// assert_eq!(data.connected_application.name, "Example");
117/// assert_eq!(data.connected_application.description, "Example-description");
118///
119/// assert_eq!(data.connected_application.permissions.len(), 3);
120/// assert_eq!(data.connected_application.permissions[0], "read");
121/// assert_eq!(data.connected_application.permissions[1], "write");
122/// assert_eq!(data.connected_application.permissions[2], "emailaddress");
123///
124/// assert_eq!(data.connected_application.approved_at, approved_at_date_time);
125/// assert_eq!(data.connected_application.id, "1111111");
126///
127/// // Re-serialize is equivalent to original data without pretty printing
128/// // assert_eq!(serde_json::to_string(&data).unwrap(), json.replace("\n", "").replace(" ", ""));
129/// ```
130#[derive(Deserialize, Serialize, Debug, Clone, Display)]
131#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
132#[serde(rename_all = "camelCase")]
133pub struct ConnectedApplicationObject {
134 /// ## Example JSON data
135 ///
136 /// ```json
137 /// {
138 /// "connectedApplication" : {
139 /// "organization" : {
140 /// "name" : "Example",
141 /// "url" : "https://example.com",
142 /// "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy",
143 /// "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service"
144 /// },
145 /// "name" : "Example",
146 /// "description" : "Example-description",
147 /// "permissions" : [
148 /// "read",
149 /// "write",
150 /// "emailaddress"
151 /// ],
152 /// "approvedAt" : "2020-01-20T21:42:09.068Z",
153 /// "id" : "1111111"
154 /// }
155 /// }
156 /// ```
157 pub connected_application: ConnectedApplication,
158}
159
160/// ## Example
161///
162/// ```
163/// use chrono::{DateTime, NaiveDateTime, Utc};
164///
165/// use twitter_archive::convert::date_time_iso_8601::FORMAT;
166///
167/// use twitter_archive::structs::connected_application::ConnectedApplication;
168///
169/// let approved_at_string = "2020-01-20T21:42:09.068Z";
170/// let approved_at_native_time = NaiveDateTime::parse_from_str(&approved_at_string, FORMAT).unwrap();
171/// let approved_at_date_time = DateTime::<Utc>::from_naive_utc_and_offset(approved_at_native_time, Utc);
172///
173/// let json = format!(r#"{{
174/// "organization" : {{
175/// "name" : "Example",
176/// "url" : "https://example.com",
177/// "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy",
178/// "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service"
179/// }},
180/// "name" : "Example",
181/// "description" : "Example-description",
182/// "permissions" : [
183/// "read",
184/// "write",
185/// "emailaddress"
186/// ],
187/// "approvedAt" : "{approved_at_string}",
188/// "id" : "1111111"
189/// }}"#);
190///
191/// let data: ConnectedApplication = serde_json::from_str(&json).unwrap();
192///
193/// // De-serialized properties
194/// assert_eq!(data.organization.name, "Example");
195/// assert_eq!(data.organization.url, "https://example.com");
196/// assert_eq!(data.organization.privacy_policy_url, "https://example.com/policy/example-privacy-policy");
197/// assert_eq!(data.organization.terms_and_conditions_url, "https://example.com/policy/medium-terms-of-service");
198///
199/// assert_eq!(data.name, "Example");
200/// assert_eq!(data.description, "Example-description");
201///
202/// assert_eq!(data.permissions.len(), 3);
203/// assert_eq!(data.permissions[0], "read");
204/// assert_eq!(data.permissions[1], "write");
205/// assert_eq!(data.permissions[2], "emailaddress");
206///
207/// assert_eq!(data.approved_at, approved_at_date_time);
208/// assert_eq!(data.id, "1111111");
209///
210/// // Re-serialize is equivalent to original data without pretty printing
211/// // assert_eq!(serde_json::to_string(&data).unwrap(), json.replace("\n", "").replace(" ", ""));
212/// ```
213#[derive(Deserialize, Serialize, Debug, Clone, Display)]
214#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
215#[serde(rename_all = "camelCase")]
216pub struct ConnectedApplication {
217 /// ## Example JSON data
218 ///
219 /// ```json
220 /// {
221 /// "organization" : {
222 /// "name" : "Example",
223 /// "url" : "https://example.com",
224 /// "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy",
225 /// "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service"
226 /// }
227 /// }
228 /// ```
229 pub organization: Organization,
230
231 /// Human readable name of application
232 ///
233 /// ## Example JSON data
234 ///
235 /// ```json
236 /// { "name" : "Example" }
237 /// ```
238 pub name: String,
239
240 /// Human readable description of application
241 ///
242 /// ## Example JSON data
243 ///
244 /// ```json
245 /// { "description" : "Example-description" }
246 /// ```
247 pub description: String,
248
249 /// List of permissions provided to application
250 ///
251 /// ## Example JSON data
252 ///
253 /// ```json
254 /// {
255 /// "permissions" : [
256 /// "read",
257 /// "write",
258 /// "emailaddress"
259 /// ]
260 /// }
261 /// ```
262 pub permissions: Vec<String>,
263
264 /// ## Example JSON data
265 ///
266 /// ```json
267 /// { "approvedAt" : "2020-01-20T21:42:09.068Z" }
268 /// ```
269 #[serde(with = "convert::date_time_iso_8601")]
270 pub approved_at: DateTime<Utc>,
271
272 /// ID of application or maybe ID of account permitting application?
273 ///
274 /// ## Example JSON data
275 ///
276 /// ```json
277 /// { "id" : "1111111" }
278 /// ```
279 pub id: String,
280}
281
282/// ## Example
283///
284/// ```
285/// use twitter_archive::structs::connected_application::Organization;
286///
287/// let json = r#"{
288/// "name" : "Example",
289/// "url" : "https://example.com",
290/// "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy",
291/// "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service"
292/// }"#;
293///
294/// let data: Organization = serde_json::from_str(&json).unwrap();
295///
296/// // De-serialized properties
297/// assert_eq!(data.name, "Example");
298/// assert_eq!(data.url, "https://example.com");
299/// assert_eq!(data.privacy_policy_url, "https://example.com/policy/example-privacy-policy");
300/// assert_eq!(data.terms_and_conditions_url, "https://example.com/policy/medium-terms-of-service");
301///
302/// // Re-serialize is equivalent to original data without pretty printing
303/// // assert_eq!(serde_json::to_string(&data).unwrap(), json.replace("\n", "").replace(" ", ""));
304/// ```
305#[derive(Deserialize, Serialize, Debug, Clone, Display)]
306#[display(fmt = "{}", "serde_json::to_value(self).unwrap()")]
307#[serde(rename_all = "camelCase")]
308pub struct Organization {
309 /// Human readable name of application
310 ///
311 /// ## Example JSON data
312 ///
313 /// ```json
314 /// { "name" : "Example" }
315 /// ```
316 pub name: String,
317
318 /// Web address of application, usually the "home" page
319 ///
320 /// ## Example JSON data
321 ///
322 /// ```json
323 /// { "url" : "https://example.com" }
324 /// ```
325 pub url: String,
326
327 /// Web address of privacy policy for application
328 ///
329 /// ## Example JSON data
330 ///
331 /// ```json
332 /// { "privacyPolicyUrl" : "https://example.com/policy/example-privacy-policy" }
333 /// ```
334 pub privacy_policy_url: String,
335
336 /// Web address of terms and conditions policy for application
337 ///
338 /// ## Example JSON data
339 ///
340 /// ```json
341 /// { "termsAndConditionsUrl" : "https://example.com/policy/medium-terms-of-service" }
342 /// ```
343 pub terms_and_conditions_url: String,
344}