groupme-rust-stats 1.0.4

A rust command line tool which downloads the complete history of a groupme chat group. It also runs a few analytics on the data and displays as a plain text file.
use std::collections::HashMap;
use serde::{Serialize};
use std::fs::File;
use std::io::Write;
use std::error;
use csv::WriterBuilder;
use crate::file_handlers::raw_messages;
use crate::groupme_api_structs::Messages;
use crate::stat_structs::UserStats;



#[derive(Serialize)]
struct Row<'a> {
    pub user_id: &'a str,
    pub user_firstname:&'a String,
    pub user_names: &'a String,
}


pub fn run_stats(user_stats: &mut HashMap<String, UserStats>) -> &mut HashMap<String, UserStats> {

	let messages: Vec<Messages> =
		raw_messages::read_all_in_dir().unwrap();

    for message in messages {
        add_stats(message, user_stats);
	}

	write_users_as_csv(user_stats.clone()).unwrap();

    return user_stats;
}


fn add_stats(
    message: Messages,
    user_stats: &mut HashMap<String, UserStats>,
) {
    let has_user_stats = user_stats.contains_key(&message.user_id);
    let user_id = message.user_id.clone();
    let key = user_id.clone();
    let lookup = key.clone();
    let user_name = message.name.clone();

    let who_liked_my_messages = HashMap::new();
    let whos_messages_did_i_like = HashMap::new();

    let cur_hash: UserStats = UserStats {
        id: user_id,
        first_name: user_name,
        names: vec![message.name].into_iter().collect(),
        avatar_urls: vec![message.avatar_url.unwrap_or_default()]
            .into_iter()
            .collect(),
        total_likes: message.favorited_by.len() as u32,
        total_messages: 1,
        top_like_message: (
            (message.favorited_by.len() as u32),
            message.text.unwrap_or_default(),
        ),
        who_liked_my_messages,
        whos_messages_did_i_like,
        ratio: "0".to_string(),
    };

    if !has_user_stats {
        user_stats.insert(key, cur_hash);
    } else {
        let found_hash = user_stats.get_mut(&lookup).unwrap();
        found_hash.total_likes += cur_hash.total_likes;
        found_hash.total_messages += cur_hash.total_messages;
        found_hash
            .who_liked_my_messages
            .extend(cur_hash.who_liked_my_messages);
        found_hash.names.extend(cur_hash.names);
        found_hash.avatar_urls.extend(cur_hash.avatar_urls);
        if cur_hash.top_like_message.0 > found_hash.top_like_message.0 {
            found_hash.top_like_message = cur_hash.top_like_message
        }

        found_hash.handle_who_liked_my_messages(&message.favorited_by);
        found_hash.ratio = found_hash.calc_likes_vs_messages();
        for fav in message.favorited_by {
            let fav_user_id = fav.clone();
            let &mut user;
            if user_stats.contains_key(&fav) {
                user = user_stats.get(&fav).unwrap().clone();
            } else {
                user = UserStats {
                    id: fav,
                    first_name: "".to_string(),
                    names: vec![].into_iter().collect(),
                    avatar_urls: vec![].into_iter().collect(),
                    total_likes: 0 as u32,
                    total_messages: 1,
                    top_like_message: (0, "".to_string()),
                    who_liked_my_messages: HashMap::new(),
                    whos_messages_did_i_like: HashMap::new(),
                    ratio: "0".to_string(),
                };
            }
            let updated_user = update_like_count(user, &cur_hash.id);
            user_stats.remove(&fav_user_id.clone());
            user_stats.insert(fav_user_id, updated_user);
        }
    }
}

fn update_like_count(mut user: UserStats, message_by_id: &String) -> UserStats {
    if !user.whos_messages_did_i_like.contains_key(message_by_id) {
        user.whos_messages_did_i_like
            .insert(message_by_id.to_string(), 1);
    } else {
        let user_liked_cur = user.whos_messages_did_i_like.get(message_by_id).unwrap();
        let user_liked_old = user_liked_cur.clone() + 1;
        user.whos_messages_did_i_like.remove(message_by_id);
        user.whos_messages_did_i_like
            .insert(message_by_id.to_string(), user_liked_old);
    }
    return user;
}


pub fn write_users_as_csv(user_stats:HashMap<String, UserStats>) -> Result<(),Box<dyn error::Error>> {
    let mut writer = WriterBuilder::new()
         .has_headers(false)
         .from_writer(vec![]);
        
    for user_stat in &user_stats{
        let names: Vec<String> = user_stat.1.names.iter().map(|name| name.clone()).collect();
        let user_names = "{".to_string() + &names.join(", ") + &"}".to_string();

        writer.serialize(Row {
            user_id: &user_stat.1.id,
            user_firstname: &user_stat.1.first_name,
            user_names: &user_names,
        })?;
    }

    let data = String::from_utf8(writer.into_inner()?)?;

    let filetype = ".csv";
    let mut filename = results_folder_name();
    filename.push_str("/users");
    filename.push_str(&filetype);

    let mut f = File::create(filename).expect("Unable to open file");

    let message_bytes = &data.as_bytes();
    f.write_all(&message_bytes).expect("Unable to write data");

    Ok(())
}

pub fn results_folder_name() -> String {
    let mut settings = config::Config::default();
    settings
		.merge(config::File::with_name("Settings")).unwrap()
		.merge(config::Environment::with_prefix("APP")).unwrap();

    return settings.get_str("results_folder")
                                        .unwrap_or_default();
}