linkedin_csv/
lib.rs

1use std::error::Error;
2use std::fs::File;
3use std::io::BufRead;
4use std::path::Path;
5
6use serde::Deserialize;
7
8#[derive(Deserialize, Debug)]
9#[serde(deny_unknown_fields)]
10#[allow(dead_code)]
11pub struct Vote {
12    #[serde(rename = "Date")]
13    pub date: String,
14
15    #[serde(rename = "Link")]
16    pub link: String,
17
18    #[serde(rename = "OptionText")]
19    pub option_text: String,
20}
21
22#[derive(Deserialize, Debug)]
23#[serde(deny_unknown_fields)]
24#[allow(dead_code)]
25pub struct SearchQuery {
26    #[serde(rename = "Time")]
27    pub time: String,
28
29    #[serde(rename = "Search Query")]
30    pub search_query: String,
31}
32
33#[derive(Deserialize, Debug)]
34#[serde(deny_unknown_fields)]
35#[allow(dead_code)]
36pub struct Message {
37    #[serde(rename = "CONVERSATION ID")]
38    pub conversation_id: String,
39
40    #[serde(rename = "CONVERSATION TITLE")]
41    pub conversation_title: String,
42
43    #[serde(rename = "FROM")]
44    pub from: String,
45
46    #[serde(rename = "SENDER PROFILE URL")]
47    pub sender_profile_url: String,
48
49    #[serde(rename = "TO")]
50    pub to: String,
51
52    #[serde(rename = "RECIPIENT PROFILE URLS")]
53    pub recipient_profile_urls: String,
54
55    #[serde(rename = "DATE")]
56    pub date: String,
57
58    #[serde(rename = "SUBJECT")]
59    pub subject: String,
60
61    #[serde(rename = "CONTENT")]
62    pub content: String,
63
64    #[serde(rename = "FOLDER")]
65    pub folder: String,
66
67    #[serde(rename = "IS MESSAGE DRAFT")]
68    pub is_message_draft: String,
69}
70
71#[derive(Deserialize, Debug)]
72#[serde(deny_unknown_fields)]
73#[allow(dead_code)]
74pub struct Connection {
75    #[serde(rename = "First Name")]
76    pub first_name: String,
77
78    #[serde(rename = "Last Name")]
79    pub last_name: String,
80
81    #[serde(rename = "URL")]
82    pub url: String,
83
84    #[serde(rename = "Email Address")]
85    pub email_address: String,
86
87    #[serde(rename = "Company")]
88    pub company: String,
89
90    #[serde(rename = "Position")]
91    pub position: String,
92
93    #[serde(rename = "Connected On")]
94    pub connected_on: String,
95}
96
97#[derive(Deserialize, Debug)]
98#[serde(deny_unknown_fields)]
99#[allow(dead_code)]
100pub struct Invitation {
101    #[serde(rename = "From")]
102    pub from: String,
103
104    #[serde(rename = "To")]
105    pub to: String,
106
107    #[serde(rename = "Sent At")]
108    pub sent_at: String,
109
110    #[serde(rename = "Message")]
111    pub message: String,
112
113    #[serde(rename = "Direction")]
114    pub direction: String,
115
116    #[serde(rename = "inviterProfileUrl")]
117    pub inviter_profile_url: String,
118
119    #[serde(rename = "inviteeProfileUrl")]
120    pub invitee_profile_url: String,
121}
122
123#[derive(Deserialize, Debug)]
124#[serde(deny_unknown_fields)]
125#[allow(dead_code)]
126pub struct Skill {
127    #[serde(rename = "Name")]
128    pub name: String,
129}
130
131#[derive(Deserialize, Debug)]
132#[serde(deny_unknown_fields)]
133#[allow(dead_code)]
134pub struct Share {
135    #[serde(rename = "Date")]
136    pub date: String,
137
138    #[serde(rename = "ShareLink")]
139    pub share_link: String,
140
141    #[serde(rename = "ShareCommentary")]
142    pub share_commentary: String,
143
144    #[serde(rename = "SharedUrl")]
145    pub shared_url: String,
146
147    #[serde(rename = "MediaUrl")]
148    pub media_url: String,
149
150    #[serde(rename = "Visibility")]
151    pub visibility: String,
152}
153
154#[derive(Deserialize, Debug)]
155#[serde(deny_unknown_fields)]
156#[allow(dead_code)]
157pub struct Contact {
158    #[serde(rename = "Source")]
159    pub source: String,
160
161    #[serde(rename = "FirstName")]
162    pub first_name: String,
163
164    #[serde(rename = "LastName")]
165    pub last_name: String,
166
167    #[serde(rename = "Companies")]
168    pub companies: String,
169
170    #[serde(rename = "Title")]
171    pub title: String,
172
173    #[serde(rename = "Emails")]
174    pub emails: String,
175
176    #[serde(rename = "PhoneNumbers")]
177    pub phone_numbers: String,
178
179    #[serde(rename = "CreatedAt")]
180    pub created_at: String,
181
182    #[serde(rename = "Addresses")]
183    pub addresses: String,
184
185    #[serde(rename = "Sites")]
186    pub sites: String,
187
188    #[serde(rename = "InstantMessageHandles")]
189    pub instant_message_handles: String,
190
191    #[serde(rename = "FullName")]
192    pub full_name: String,
193
194    #[serde(rename = "Birthday")]
195    pub birthday: String,
196
197    #[serde(rename = "Location")]
198    pub location: String,
199
200    #[serde(rename = "BookmarkedAt")]
201    pub bookmarked_at: String,
202
203    #[serde(rename = "Profiles")]
204    pub profiles: String,
205}
206
207macro_rules! read_file {
208    ($name:ident, $struct:ty, $file:expr) => {
209        pub fn $name(path: &Path) -> Result<Vec<$struct>, Box<dyn Error>> {
210            let filepath = path.join($file);
211            let mut records: Vec<$struct> = vec![];
212            let fh = File::open(&filepath)?;
213            let mut rdr = csv::Reader::from_reader(fh);
214            for result in rdr.deserialize() {
215                let record: $struct = result?;
216                records.push(record);
217            }
218            Ok(records)
219        }
220    };
221}
222
223read_file!(read_contacts_file, Contact, "Contacts.csv");
224read_file!(read_skills_file, Skill, "Skills.csv");
225read_file!(read_shares_file, Share, "Shares.csv");
226read_file!(read_invitations_file, Invitation, "Invitations.csv");
227read_file!(read_messages_file, Message, "messages.csv");
228read_file!(read_votes_file, Vote, "Votes.csv");
229
230pub fn read_connections_file(path: &Path) -> Result<Vec<Connection>, Box<dyn Error>> {
231    let filepath = path.join("Connections.csv");
232    let mut records: Vec<Connection> = vec![];
233    let fh = File::open(&filepath)?;
234    let mut br = std::io::BufReader::new(&fh);
235
236    // This file has the following message at the top, we need to skip that.
237    // Notes:
238    // "When exporting your connection data, you may notice that some of the email addresses are missing. You will only see email addresses for connections who have allowed their connections to see or download their email address using this setting https://www.linkedin.com/psettings/privacy/email. You can learn more here https://www.linkedin.com/help/linkedin/answer/261"
239
240    for _ in 0..3 {
241        br.skip_until(b'\n')
242            .expect("expecting Notes before the header");
243    }
244
245    let mut rdr = csv::Reader::from_reader(br);
246    for result in rdr.deserialize() {
247        let record: Connection = result?;
248        records.push(record);
249    }
250    Ok(records)
251}
252
253pub fn read_search_queries_file(path: &Path) -> Result<Vec<SearchQuery>, Box<dyn Error>> {
254    let filepath = path.join("SearchQueries.csv");
255    let mut records: Vec<SearchQuery> = vec![];
256    let fh = File::open(&filepath)?;
257    let mut rdr = csv::Reader::from_reader(fh);
258    for result in rdr.deserialize() {
259        let record: SearchQuery = result?;
260        records.push(record);
261    }
262    Ok(records)
263}
264
265#[cfg(test)]
266mod tests {
267
268    #[test]
269    fn it_works() {
270        let result = 42;
271        assert_eq!(result, 42);
272    }
273}