diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a43f27..b535de5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,23 +6,13 @@ env: RUSTFLAGS: "-Dwarnings" jobs: - clippy_check: + checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Build + run: cargo build - name: Run Clippy - run: cargo clippy --all-targets --all-features - - format_check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Run Rustfmt + run: cargo clippy --no-deps + - name: Run Rustfmt (run `cargo fmt --all` to fix) run: cargo fmt --all -- --check - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --all-targets --all-features diff --git a/Cargo.toml b/Cargo.toml index 2b45df0..9b9fd48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ imap = "3.0.0-alpha.12" imap-proto = "0.16.3" itertools = "0.12.0" log = "0.4.20" +mailparse = "0.14.1" native-tls = "0.2.11" open = "5.0.1" pretty_env_logger = "0.5.0" diff --git a/README.md b/README.md index b6cf188..e6af733 100644 --- a/README.md +++ b/README.md @@ -37,20 +37,23 @@ The configuration file is written in [TOML](https://toml.io). To login to your E server = "my.mail.server" # The port to connect to port = 993 -# (Optional) The username to use for authentication -# username = "my_username" -# (Optional) The password to use for authentication -# password = "my_password" ``` The configure which E-Mails to search for and fetch, you need to provide the following information: ```toml [mail.fetch] -# The mailboxes to fetch from the WRs you sent, you can also run `cargo run mailboxes` to get a list of all mailboxes you have. +# The mailboxes to fetch from the WRs you sent, +# you can also run `cargo run mailboxes` to get a list of all mailboxes you have. wr_mailboxes = ["Sent", "Sent Messages"] -# The mailboxes to fetch from the WR replies you received. Usually you only need to fetch from the INBOX. However, if you have a rule that moves the WR replies to a different mailbox, you need to add it here. +# The mailboxes to fetch from the WR replies you received. +# Usually you only need to fetch from the INBOX. However, +# if you have a rule that moves the WR replies to a different mailbox, +# you need to add it here. re_mailboxes = ["INBOX"] -# The pattern to match the WR subject you sent. This will match all subjects that contain the strings "WR" OR "Weekly Report". This means that your Subject needs to be consistent over the years. Currently, you can only match at most two patterns (this is a limitation of the IMAP search query). +# The pattern to match the WR subject you sent. +# This will match all subjects that contain the strings "WR" OR "Weekly Report". +# This means that your Subject needs to be consistent over the years. +# Currently, you can only match at most two patterns (this is a limitation of the IMAP search query). pattern = ["WR", "Weekly Report"] # From which mail address you sent the WRs from = "my_username@my.mail.server" @@ -60,14 +63,6 @@ to = "theboss@my.mail.server" year = 2023 ``` -Lastly, you have to configure a few things how the statistics are generated: - -```toml -[stats] -# The number of holiday weeks you were not working. This includes holidays, sick days, etc. -num_holidays = 5 -``` - ## How it works The script works by connecting to your E-Mail account using IMAP. It then searches for all E-Mails that match the given criteria. For instance it creates an IMAP search query that looks like this: @@ -76,7 +71,7 @@ The script works by connecting to your E-Mail account using IMAP. It then search FROM "my_username@my.mail.server" TO "theboss@my.mail.server" SUBJECT "WR" OR SUBJECT "Weekly Report" SINCE 01-Jan-2023 BEFORE 31-Dec-2023 ``` -which will return a sequence of E-Mail IDs. The script then fetches only the header (or `ENVELOPE` in IMAP terms) of each E-Mail, which contains information such as the date, the sender, the recipient, etc. The content of the mail is not fetched at all. The script then parses the date and sender information to create a list of WRs. +which will return a sequence of E-Mail IDs. The script then fetches first the header (or `ENVELOPE` in IMAP terms) of each E-Mail, which contains information such as the date, the sender, the recipient, etc. In a second step, the content (or `BODY` in IMAP terms) is fetched and merged with the header to create a list of WRs. The replies are fetched in a similar way, but the other way around: diff --git a/config.toml b/config.toml index e3cf7f9..f532b99 100644 --- a/config.toml +++ b/config.toml @@ -3,17 +3,23 @@ server = "my.mail.server" # The port to connect to port = 993 -# (Optional) The username to use for authentication -# username = "my_username" -# (Optional) The password to use for authentication -# password = "my_password" [mail.fetch] -# The mailboxes to fetch from the WRs you sent, you can also run `cargo run mailboxes` to get a list of all mailboxes you have. -wr_mailboxes = ["Sent", "Sent Messages"] -# The mailboxes to fetch from the WR replies you received. Usually you only need to fetch from the INBOX. However, if you have a rule that moves the WR replies to a different mailbox, you need to add it here. +# The mailboxes to fetch from the WRs you sent, +# you can also run `cargo run mailboxes` to get +# a list of all mailboxes you have. +wr_mailboxes = ["Sent"] +# The mailboxes to fetch from the WR replies you received. +# Usually you only need to fetch from the INBOX. However, +# if you have a rule that moves the WR replies to a +# different mailbox, you need to add it here. re_mailboxes = ["INBOX"] -# The pattern to match the WR subject you sent. This will match all subjects that contain the strings "WR" OR "Weekly Report". This means that your Subject needs to be consistent over the years. Currently, you can only match at most two patterns (this is a limitation of the IMAP search query). +# The pattern to match the WR subject you sent. +# This will match all subjects that contain the +# strings "WR" OR "Weekly Report". This means that +# your Subject needs to be consistent over the years. +# Currently, you can only match at most two patterns +# (this is a limitation of the IMAP search query). pattern = ["WR", "Weekly Report"] # From which mail address you sent the WRs from = "my_username@my.mail.server" @@ -21,7 +27,3 @@ from = "my_username@my.mail.server" to = "theboss@my.mail.server" # The year to fetch the WRs from year = 2023 - -[stats] -# The number of holiday weeks you were not working. This includes holidays, sick days, etc. -num_holidays = 5 diff --git a/src/config.rs b/src/config.rs index 5631eb7..9499d5e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,19 +1,11 @@ use serde::Deserialize; -#[derive(Deserialize, Debug)] -pub struct Config { - // The mail configuration - pub mail: MailConfig, - // The statistics configuration - pub stats: StatsConfig, -} - #[derive(Deserialize, Debug)] pub struct MailConfig { // The login configuration - pub login: MailLogin, + pub server: MailLogin, // The fetch configuration - pub fetch: MailFetch, + pub query: MailQuery, } #[derive(Deserialize, Debug)] @@ -29,7 +21,7 @@ pub struct MailLogin { } #[derive(Deserialize, Debug, Clone)] -pub struct MailFetch { +pub struct MailQuery { // The mailboxes to fetch from the WRs you sent pub wr_mailboxes: Vec, // The mailboxes to fetch from the WR replies you received @@ -43,10 +35,3 @@ pub struct MailFetch { // The year to fetch the WRs from pub year: u32, } - -#[derive(Deserialize, Debug)] -pub struct StatsConfig { - // The number of holiday weeks you took this year, where you didn't had - // to write a WR, this includes sick days, vacation, etc. - pub num_holidays: u32, -} diff --git a/src/error.rs b/src/error.rs index 86c1ab5..92325ab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,7 +7,7 @@ pub enum WrError { // IO Error IoError(std::io::Error), // Error from the IMAP crate - ImapError(imap::error::Error), + ImapError(String), // Query Error QueryError(String), // Config Error @@ -16,6 +16,8 @@ pub enum WrError { SerializationError(String), // Server Error ServerError(String), + // Mail parsing error + MailParseError(String), } impl std::fmt::Display for WrError { @@ -27,6 +29,7 @@ impl std::fmt::Display for WrError { WrError::ConfigError(e) => write!(f, "Config error: {}", e), WrError::SerializationError(e) => write!(f, "Serialization error: {}", e), WrError::ServerError(e) => write!(f, "Server error: {}", e), + WrError::MailParseError(e) => write!(f, "Mail parse error: {}", e), } } } @@ -39,7 +42,7 @@ impl From for WrError { impl From for WrError { fn from(error: imap::error::Error) -> Self { - WrError::ImapError(error) + WrError::ImapError(error.to_string()) } } @@ -49,4 +52,10 @@ impl From for WrError { } } +impl From for WrError { + fn from(error: mailparse::MailParseError) -> Self { + WrError::MailParseError(error.to_string()) + } +} + impl std::error::Error for WrError {} diff --git a/src/mail.rs b/src/mail.rs index 2215592..b51a061 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -5,10 +5,11 @@ extern crate native_tls; use chrono::{DateTime, FixedOffset}; use imap::ImapConnection; use itertools::join; +use log::debug; use log::{info, warn}; use std::str::from_utf8; -use crate::config::{MailConfig, MailFetch, MailLogin}; +use crate::config::{MailConfig, MailLogin, MailQuery}; use crate::error::{Result, WrError}; #[derive(Debug, Clone)] @@ -84,6 +85,13 @@ impl Envelope { } } +#[derive(Debug, Clone)] +pub struct Mail { + pub id: u32, + pub env: Envelope, + pub body: Option, +} + fn imap_login(login: &MailLogin) -> Result>> { let domain = login.server.as_str(); let port = login.port; @@ -101,7 +109,7 @@ fn imap_login(login: &MailLogin) -> Result pub fn list_mailboxes(config: &MailConfig) -> Result<()> { // Login to the IMAP server - let mut imap_session = imap_login(&config.login)?; + let mut imap_session = imap_login(&config.server)?; // List all mailboxes let mailboxes = imap_session.list(Some(""), Some("*"))?; @@ -117,7 +125,7 @@ pub fn list_mailboxes(config: &MailConfig) -> Result<()> { pub fn fetch_inbox(config: &MailConfig) -> Result<()> { // Login to the IMAP server - let mut imap_session = imap_login(&config.login)?; + let mut imap_session = imap_login(&config.server)?; // Select the INBOX mailbox imap_session.select("INBOX")?; @@ -141,7 +149,7 @@ pub fn fetch_inbox(config: &MailConfig) -> Result<()> { Ok(()) } -fn build_imap_search_query(fetch: &MailFetch) -> Result { +fn build_imap_search_query(fetch: &MailQuery) -> Result { // Check that patterns is not empty if fetch.pattern.is_empty() { return Err(WrError::QueryError("No pattern specified".to_string())); @@ -174,17 +182,36 @@ fn build_imap_search_query(fetch: &MailFetch) -> Result { Ok(query) } -pub fn fetch_wrs(config: &MailConfig) -> Result> { +fn get_plain_text(mail: &mailparse::ParsedMail) -> Result { + if mail.subparts.is_empty() && mail.ctype.mimetype == "text/plain" { + let body = mail + .get_body() + .map_err(|_| WrError::MailParseError("Failed to parse mail body".to_string()))?; + return Ok(body); + } + let mut body_str: String = String::new(); + for part in mail.subparts.iter() { + let body = part.get_body()?; + if part.ctype.mimetype == "text/plain" { + body_str.push_str(&body); + } else { + body_str.push_str(&get_plain_text(part)?); + } + } + Ok(body_str) +} + +pub fn fetch_wrs(config: &MailConfig) -> Result> { // Login to the IMAP server - let mut imap_session = imap_login(&config.login)?; + let mut imap_session = imap_login(&config.server)?; // Search for messages that contain the pattern - let query = build_imap_search_query(&config.fetch)?; + let query = build_imap_search_query(&config.query)?; // List of WRs let mut wrs = Vec::new(); - for mailbox in config.fetch.wr_mailboxes.iter() { + for mailbox in config.query.wr_mailboxes.iter() { // Select the mailbox match imap_session.select(mailbox) { Ok(_) => {} @@ -199,8 +226,11 @@ pub fn fetch_wrs(config: &MailConfig) -> Result> { let mut sequence_set: Vec<_> = sequence_set.into_iter().collect(); sequence_set.sort(); let sequence_set: String = join(sequence_set.into_iter().map(|s| s.to_string()), ","); + // Fetch the messages + info!("Fetching envelope of {} potential WRs", sequence_set.len()); let messages = imap_session.fetch(sequence_set, "ENVELOPE")?; + debug!("Got {} messages", messages.len()); // Print the subjects of the messages for message in messages.iter() { @@ -210,32 +240,57 @@ pub fn fetch_wrs(config: &MailConfig) -> Result> { match envelope.in_reply_to { None => { let env = Envelope::from_imap_envelope(envelope); - wrs.push(env); + debug!("Found WR with subject: {}", env.subject); + wrs.push(Mail { + id: message.message, + env, + body: None, + }); } Some(_) => { let subject = from_utf8(envelope.subject.as_ref().unwrap().as_ref()) .expect("No subject in the envelope"); if reply_pattern.iter().any(|&s| subject.contains(s)) { + debug!("Skipping reply with subject: {}", subject); continue; } let env = Envelope::from_imap_envelope(envelope); - wrs.push(env); + wrs.push(Mail { + id: message.message, + env, + body: None, + }); } }; } - } - info!("Found {} WRs", wrs.len()); + info!("Found {} WRs", wrs.len()); + + // Construct a new sequence set of all the WRs + let sequence_set: Vec = wrs.iter().map(|m: &Mail| m.id).collect(); + let sequence_set: String = join(sequence_set.into_iter().map(|s| s.to_string()), ","); + + // Fetch the bodies of the WRs + info!("Fetching bodies of {} WRs", wrs.len()); + let messages = imap_session.fetch(sequence_set, "BODY[]")?; + + // Add the text of the body to the WRs + for (message, wr) in messages.iter().zip(wrs.iter_mut()) { + let body = message.body().unwrap(); + let parsed_mail = mailparse::parse_mail(body)?; + wr.body = get_plain_text(&parsed_mail).ok(); // If an error occurs, just skip the body + } + } imap_session.logout()?; Ok(wrs) } -pub fn fetch_replies(config: &MailConfig) -> Result> { +pub fn fetch_replies(config: &MailConfig) -> Result> { // Login to the IMAP server - let mut imap_session = imap_login(&config.login)?; + let mut imap_session = imap_login(&config.server)?; - let mut reply_fetch = config.fetch.clone(); + let mut reply_fetch = config.query.clone(); // Swap `from` and `to` in the fetch configuration std::mem::swap(&mut reply_fetch.from, &mut reply_fetch.to); @@ -245,7 +300,7 @@ pub fn fetch_replies(config: &MailConfig) -> Result> { // List of WRs let mut wr_replies = Vec::new(); - for mailbox in config.fetch.re_mailboxes.iter() { + for mailbox in config.query.re_mailboxes.iter() { // Select the mailbox match imap_session.select(mailbox) { Ok(_) => {} @@ -262,6 +317,7 @@ pub fn fetch_replies(config: &MailConfig) -> Result> { let sequence_set: String = join(sequence_set.into_iter().map(|s| s.to_string()), ","); // Fetch the messages + info!("Fetching {} potential Replies", sequence_set.len()); let messages = imap_session.fetch(sequence_set, "ENVELOPE")?; // Print the subjects of the messages @@ -271,7 +327,11 @@ pub fn fetch_replies(config: &MailConfig) -> Result> { match envelope.in_reply_to { Some(_) => { let env = Envelope::from_imap_envelope(envelope); - wr_replies.push(env); + wr_replies.push(Mail { + id: message.message, + env, + body: None, + }); } None => continue, }; diff --git a/src/main.rs b/src/main.rs index b817cfe..3151d17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,10 +30,10 @@ async fn main() -> Result<()> { let config_contents = fs::read_to_string("config.toml") .map_err(|_| WrError::ConfigError("Could not read config file".to_string()))?; - let mut config: config::Config = toml::from_str(&config_contents) + let mut mail_config: config::MailConfig = toml::from_str(&config_contents) .map_err(|_| WrError::ConfigError("Could not parse config file".to_string()))?; - let username = match config.mail.login.username { + let username = match mail_config.server.username { Some(username) => Some(username), None => { eprint!("Username: "); @@ -43,28 +43,24 @@ async fn main() -> Result<()> { } }; - let password = match config.mail.login.password { + let password = match mail_config.server.password { Some(password) => Some(password), None => Some(rpassword::prompt_password("Password: ").unwrap()), }; - config.mail.login.username = username; - config.mail.login.password = password; + mail_config.server.username = username; + mail_config.server.password = password; let matches = cli().get_matches(); match matches.subcommand() { - Some(("mailboxes", _)) => mail::list_mailboxes(&config.mail), - Some(("fetch-inbox", _)) => mail::fetch_inbox(&config.mail), + Some(("mailboxes", _)) => mail::list_mailboxes(&mail_config), + Some(("fetch-inbox", _)) => mail::fetch_inbox(&mail_config), _ => { - let wrs = mail::fetch_wrs(&config.mail)?; - let replies = mail::fetch_replies(&config.mail)?; + let wrs = mail::fetch_wrs(&mail_config)?; + let replies = mail::fetch_replies(&mail_config)?; let merged_wrs = wr::merge_wrs(&wrs, &replies); - let stats = stats::Stats::from_wrs( - &merged_wrs, - config.mail.fetch.year, - config.stats.num_holidays, - ); + let stats = stats::Stats::from_wrs(&merged_wrs, mail_config.query.year); stats.write_to_file("shared/stats.json")?; let localhost = "127.0.0.1:8080"; let url = format!("http://{}/", localhost); diff --git a/src/stats.rs b/src/stats.rs index 571e0e1..fe90bce 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -19,8 +19,8 @@ pub struct Stats { pub num_replied_wrs: usize, // The ratio of WRs that were replied to pub ratio_replied_wrs: f64, - // The number of skipped WRs - pub num_skipped_wrs: usize, + // The number of words written + pub num_words: usize, // The average delay of the WRs pub avg_wr_delay: f64, // The average delay of the replied WRs @@ -38,13 +38,13 @@ pub struct Stats { } impl Stats { - pub fn from_wrs(wrs: &WRs, year: u32, num_holidays: u32) -> Self { + pub fn from_wrs(wrs: &WRs, year: u32) -> Self { Stats { year, num_wrs: wrs.num_wrs(), num_replied_wrs: wrs.num_replied_wrs(), ratio_replied_wrs: wrs.ratio_replied_wrs(), - num_skipped_wrs: wrs.num_skipped_wrs(num_holidays), + num_words: wrs.num_words(), avg_wr_delay: wrs.avg_wr_delay(), avg_reply_delay: wrs.avg_reply_delay(), weekday_wr_histogram: wrs.weekday_wr_histogram(), diff --git a/src/wr.rs b/src/wr.rs index a0c1cc1..fc05fdc 100644 --- a/src/wr.rs +++ b/src/wr.rs @@ -2,49 +2,48 @@ use chrono::{Datelike, Timelike}; use log::info; use std::collections::HashMap; -use crate::mail::Envelope; - -pub fn merge_wrs(wr: &[Envelope], wr_re: &[Envelope]) -> WRs { - let mut wrs = WRs::new(); - - for w in wr.iter() { - let mut wr = WR::new(w.clone(), None); - for r in wr_re.iter() { - if let Some(message_id) = wr.sent.message_id.as_ref() { - if let Some(in_reply_to) = r.in_reply_to.as_ref() { +use crate::mail::Mail; + +pub fn merge_wrs(wrs: &[Mail], wrs_re: &[Mail]) -> WRs { + let mut merged_wrs = WRs::new(); + for wr_mail in wrs.iter() { + let mut wr = WR::new(wr_mail.clone(), None); + for re_mail in wrs_re.iter() { + if let Some(message_id) = wr_mail.env.message_id.as_ref() { + if let Some(in_reply_to) = re_mail.env.in_reply_to.as_ref() { if message_id.eq(in_reply_to) { - wr.reply = Some(r.clone()); + wr.reply = Some(re_mail.clone()); break; } } } } - wrs.wrs.push(wr); + merged_wrs.wrs.push(wr); } info!( - "Found {} Replies to {} WRs", - wrs.num_replied_wrs(), - wrs.num_wrs() + "Merged {} Replies with {} WRs", + merged_wrs.num_replied_wrs(), + merged_wrs.num_wrs() ); - wrs + merged_wrs } #[derive(Debug)] pub struct WR { // The Envelope of the WR that was sent - pub sent: Envelope, + pub sent: Mail, // The Envelope of the WR reply that was received, if any - pub reply: Option, + pub reply: Option, } impl WR { - pub fn new(sent: Envelope, reply: Option) -> Self { + pub fn new(sent: Mail, reply: Option) -> Self { WR { sent, reply } } pub fn wr_delay(&self) -> i64 { - let weekday = self.sent.date.weekday(); + let weekday = self.sent.env.date.weekday(); let days_since_friday = (weekday.num_days_from_monday() + 2) % 7; days_since_friday as i64 } @@ -52,14 +51,21 @@ impl WR { pub fn reply_delay(&self) -> Option { match self.reply { Some(ref reply) => { - let sent_date = self.sent.date; - let reply_date = reply.date; + let sent_date = self.sent.env.date; + let reply_date = reply.env.date; let duration = reply_date.signed_duration_since(sent_date); Some(duration.num_days()) } None => None, } } + + pub fn num_words(&self) -> usize { + match self.sent.body { + Some(ref body) => body.split_whitespace().count(), + None => 0, + } + } } #[derive(Debug, Default)] @@ -97,10 +103,8 @@ impl WRs { self.wrs.iter().filter(|wr| wr.reply.is_some()).count() } - pub fn num_skipped_wrs(&self, num_holidays: u32) -> usize { - let num_holidays = num_holidays as usize; - let num_wrs = self.num_wrs(); - 52 - num_holidays - num_wrs + pub fn num_words(&self) -> usize { + self.wrs.iter().map(|wr| wr.num_words()).sum() } pub fn ratio_replied_wrs(&self) -> f64 { @@ -128,7 +132,7 @@ impl WRs { hist.insert(day, 0); } for wr in self.wrs.iter() { - let weekday = wr.sent.date.weekday(); + let weekday = wr.sent.env.date.weekday(); hist.entry(weekday as u32).and_modify(|e| *e += 1); } hist @@ -143,7 +147,7 @@ impl WRs { for wr in self.wrs.iter() { match wr.reply { Some(_) => { - let weekday = wr.sent.date.weekday(); + let weekday = wr.sent.env.date.weekday(); hist.entry(weekday as u32).and_modify(|e| *e += 1); } None => continue, @@ -159,7 +163,7 @@ impl WRs { hist.insert(hour, 0); } for wr in self.wrs.iter() { - let hour = wr.sent.date.hour(); + let hour = wr.sent.env.date.hour(); hist.entry(hour).and_modify(|e| *e += 1); } hist @@ -174,7 +178,7 @@ impl WRs { for wr in self.wrs.iter() { match wr.reply { Some(_) => { - let hour = wr.sent.date.hour(); + let hour = wr.sent.env.date.hour(); hist.entry(hour).and_modify(|e| *e += 1); } None => continue, @@ -187,7 +191,7 @@ impl WRs { let mut hist = HashMap::new(); for wr in self.wrs.iter() { - match wr.sent.cc { + match wr.sent.env.cc { Some(ref cc) => { for addr in cc.iter() { if let Some(user) = &addr.user { diff --git a/web/css/styles.css b/web/css/styles.css index 198a5e9..5c49828 100644 --- a/web/css/styles.css +++ b/web/css/styles.css @@ -129,7 +129,7 @@ body { justify-content: center; } -#num-skipped-wrs-tile { +#num-words-tile { grid-column: 1 / 3; grid-row: 2 / 3; display: flex; diff --git a/web/index.html b/web/index.html index 8dba6ef..06006b2 100644 --- a/web/index.html +++ b/web/index.html @@ -13,11 +13,12 @@
-
written...
+
written
-
-
-
...skipped
+
+
containing
+
+
words
diff --git a/web/js/main.js b/web/js/main.js index 0ecda94..2cdf2d2 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -387,7 +387,7 @@ function switchPalette(year) { document.addEventListener('DOMContentLoaded', function() { const yearId = 'wrapped-year'; const numWrsWrittenId = 'num-wrs-written'; - const numWrsSkippedId = 'num-wrs-skipped'; + const numWordsId = 'num-words'; const progressCircleId = 'progress-circle'; const ratioTextOverlayId = 'ratio-text-overlay'; const delayOfReplyId = 'delay-of-reply'; @@ -398,7 +398,7 @@ document.addEventListener('DOMContentLoaded', function() { let weekdayData; let timeofdayData; let numWrsWritten; - let numWrsSkipped; + let numWords; let ccData; const numWrsWrittenContainer= document.getElementById(numWrsWrittenId); @@ -407,10 +407,10 @@ document.addEventListener('DOMContentLoaded', function() { numWrsWrittenContainer.textContent = numWrsText; } - const numWrsSkippedContainer = document.getElementById(numWrsSkippedId); - function updateNumWrsSkipped(numWrs) { - const numWrsText = numWrs + " WRs" - numWrsSkippedContainer.textContent = numWrsText; + const numWordsContainer = document.getElementById(numWordsId); + function updateNumWords(NumWords) { + const NumWordsText = parseInt(parseInt(NumWords) / 1000) + "k" + numWordsContainer.textContent = NumWordsText; } function resizeProgressCircleChart() { @@ -455,14 +455,14 @@ document.addEventListener('DOMContentLoaded', function() { switchPalette(year); ratioRepliedWRs = data.ratio_replied_wrs; numWrsWritten = data.num_wrs; - numWrsSkipped = data.num_skipped_wrs; + numWords = data.num_words; delayDays = data.avg_reply_delay; weekdayData = data.weekday_wr_histogram; timeofdayData = data.hour_reply_histogram; ccData = data.cc_histogram; updateYear(year); updateNumWrsWritten(numWrsWritten); - updateNumWrsSkipped(numWrsSkipped); + updateNumWords(numWords); updateTextOverlay(ratioRepliedWRs); updateDelay(delayDays); updateCCList(ccData);