feat: Implemented a whatsapp and minor changes
This commit is contained in:
parent
7c23064afe
commit
05b31e426e
153
Cargo.lock
generated
153
Cargo.lock
generated
@ -37,6 +37,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
@ -139,6 +154,12 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecount"
|
name = "bytecount"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
@ -195,6 +216,20 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@ -270,6 +305,12 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@ -488,6 +529,29 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@ -518,6 +582,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.76"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -546,6 +620,12 @@ dependencies = [
|
|||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
@ -577,6 +657,7 @@ name = "msgexport"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"encoding",
|
"encoding",
|
||||||
@ -595,6 +676,15 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.5"
|
||||||
@ -1081,6 +1171,69 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.90",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.90",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.99"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -15,3 +15,4 @@ encoding = "0.2"
|
|||||||
zip = "0.6"
|
zip = "0.6"
|
||||||
tokio-util = "0.7"
|
tokio-util = "0.7"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
|
chrono = "0.4.38"
|
||||||
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, process::exit};
|
|||||||
|
|
||||||
use dialoguer::Select;
|
use dialoguer::Select;
|
||||||
|
|
||||||
use crate::{services::{instagram::Instagram, service::Service}, types::{participant::Participant, project::Project, user::User}};
|
use crate::{services::{instagram::instagram::Instagram, service::Service, whatsapp::whatsapp::WhatsApp}, types::{participant::Participant, project::Project, user::User}};
|
||||||
|
|
||||||
pub async fn add(path: String) {
|
pub async fn add(path: String) {
|
||||||
let mut raw_project = Project::load(path.clone()).await.unwrap_or_else(|e| {
|
let mut raw_project = Project::load(path.clone()).await.unwrap_or_else(|e| {
|
||||||
@ -19,6 +19,9 @@ pub async fn add(path: String) {
|
|||||||
Box::new(Instagram {
|
Box::new(Instagram {
|
||||||
data: None
|
data: None
|
||||||
}),
|
}),
|
||||||
|
Box::new(WhatsApp {
|
||||||
|
data: None
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
let options: Vec<&'static str> = services.iter().map(|service| service.get_name()).collect();
|
let options: Vec<&'static str> = services.iter().map(|service| service.get_name()).collect();
|
||||||
@ -32,30 +35,41 @@ pub async fn add(path: String) {
|
|||||||
|
|
||||||
let service = services.get(selected).unwrap();
|
let service = services.get(selected).unwrap();
|
||||||
let mut final_service = service.setup();
|
let mut final_service = service.setup();
|
||||||
let _ = final_service.load().await;
|
|
||||||
|
println!("Loading the project file");
|
||||||
|
final_service.load().await.unwrap_or_else(|e| {
|
||||||
|
println!("Error while loading {} export: {}", final_service.get_name(), e);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
let participants = final_service.get_participants();
|
let participants = final_service.get_participants();
|
||||||
|
|
||||||
|
if participants.len() > raw_project.project.users.len() {
|
||||||
|
eprintln!("You have {} users in your project but in the {} chat there are {} participants!", raw_project.project.users.len(), final_service.get_name(), participants.len());
|
||||||
|
println!("You need to have equal amount or more users in project that participants in chat!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let mut user_map: HashMap<Participant, User> = HashMap::new();
|
let mut user_map: HashMap<Participant, User> = HashMap::new();
|
||||||
|
|
||||||
println!("Now select what project user is coresponding to a {} participant", service.get_name());
|
println!("Now select what project user is coresponding to a {} participant", service.get_name());
|
||||||
for user in raw_project.project.users.clone() {
|
for participant in participants {
|
||||||
let parts: Vec<Participant> = participants.iter().filter(|participant| {
|
let users: Vec<User> = raw_project.project.users.iter().filter(|user| {
|
||||||
!user_map.contains_key(&participant)
|
!user_map.values().any(|x| x.name == user.name)
|
||||||
}).map(|e| e.clone()).collect();
|
}).map(|e| e.clone()).collect();
|
||||||
|
|
||||||
let options: Vec<String> = parts.iter().map(|part| part.name.clone()).collect();
|
let options: Vec<String> = users.iter().map(|usr| usr.name.clone()).collect();
|
||||||
|
|
||||||
let selected = Select::new()
|
let selected = Select::new()
|
||||||
.with_prompt(format!("Select a {} participant for user '{}'", service.get_name(), user.name))
|
.with_prompt(format!("Select a {} user for participant '{}'", service.get_name(), participant.name))
|
||||||
.items(&options)
|
.items(&options)
|
||||||
.default(0)
|
.default(0)
|
||||||
.interact()
|
.interact()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let selected_part = parts.get(selected).unwrap().clone();
|
let selected_user = users.get(selected).unwrap().clone();
|
||||||
|
|
||||||
user_map.insert(selected_part.clone(), user);
|
user_map.insert(participant, selected_user);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Starting to merge messages...");
|
println!("Starting to merge messages...");
|
||||||
|
39
src/commands/export.rs
Normal file
39
src/commands/export.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use dialoguer::Select;
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::types::project::Project;
|
||||||
|
|
||||||
|
pub async fn export(path: String) {
|
||||||
|
let raw_project = Project::load(path.clone()).await.unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error while loading a project: {}", e);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let options = vec!["HTML", "JSON (without media)"];
|
||||||
|
|
||||||
|
let selected = Select::new()
|
||||||
|
.with_prompt("Choose a output type")
|
||||||
|
.items(&options)
|
||||||
|
.default(0)
|
||||||
|
.interact()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match selected {
|
||||||
|
0 => {
|
||||||
|
// HTML
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// JSON (without media)
|
||||||
|
let content = raw_project.project.to_projectjson();
|
||||||
|
let json_content = serde_json::to_string(&content).unwrap();
|
||||||
|
|
||||||
|
let mut file = File::create("./export.json").await.expect("Error while creating a file.");
|
||||||
|
let _ = file.write_all(json_content.as_bytes()).await;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid option");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod users;
|
pub mod users;
|
||||||
pub mod add;
|
pub mod add;
|
||||||
|
pub mod export;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::process::exit;
|
use std::{collections::HashSet, process::exit};
|
||||||
|
|
||||||
use dialoguer::{Input, Select};
|
use dialoguer::{Input, Select};
|
||||||
use tabled::Table;
|
use tabled::Table;
|
||||||
@ -72,6 +72,21 @@ pub async fn menu(mut project_raw: ProjectRaw, path: String) {
|
|||||||
.default(0)
|
.default(0)
|
||||||
.interact()
|
.interact()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let delete_name = options_users.get(option_remove).unwrap();
|
||||||
|
|
||||||
|
let mut participants_in_chat: HashSet<String> = HashSet::new();
|
||||||
|
for message in project.messages.clone() {
|
||||||
|
participants_in_chat.insert(message.sender_name);
|
||||||
|
|
||||||
|
for reaction in message.reactions {
|
||||||
|
participants_in_chat.insert(reaction.actor_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if participants_in_chat.contains(delete_name) {
|
||||||
|
eprintln!("This user has some messages in chat, therefore cannot be removed!")
|
||||||
|
}
|
||||||
|
|
||||||
project.users.remove(option_remove);
|
project.users.remove(option_remove);
|
||||||
|
|
||||||
|
11
src/main.rs
11
src/main.rs
@ -5,7 +5,7 @@ mod services;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use clap::{command, ArgGroup, Parser};
|
use clap::{command, ArgGroup, Parser};
|
||||||
use commands::{add::add, users::users};
|
use commands::{add::add, export::export, users::users};
|
||||||
use types::project::Project;
|
use types::project::Project;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@ -15,7 +15,7 @@ use types::project::Project;
|
|||||||
about = "Export and merge your messages between platforms",
|
about = "Export and merge your messages between platforms",
|
||||||
author = "Jakub Žitník",
|
author = "Jakub Žitník",
|
||||||
group = ArgGroup::new("command")
|
group = ArgGroup::new("command")
|
||||||
.args(&["new", "users", "add"])
|
.args(&["new", "users", "add", "export"])
|
||||||
.multiple(false)
|
.multiple(false)
|
||||||
.required(true)
|
.required(true)
|
||||||
)]
|
)]
|
||||||
@ -28,6 +28,9 @@ struct Cli {
|
|||||||
|
|
||||||
#[arg(long = "add", help = "Add messages from a service.", value_name = "PROJECT_FILE")]
|
#[arg(long = "add", help = "Add messages from a service.", value_name = "PROJECT_FILE")]
|
||||||
add: Option<String>,
|
add: Option<String>,
|
||||||
|
|
||||||
|
// #[arg(long = "export", help = "Export all the chats in specific format", value_name = "PROJECT_FILE")]
|
||||||
|
// export: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -53,4 +56,8 @@ async fn main() {
|
|||||||
if let Some(path) = cli.add {
|
if let Some(path) = cli.add {
|
||||||
add(path).await;
|
add(path).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if let Some(path) = cli.export {
|
||||||
|
// export(path).await;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -7,81 +7,15 @@ use encoding::{all::ISO_8859_1, EncoderTrap, Encoding};
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::{fs::File, io::AsyncReadExt};
|
use tokio::{fs::File, io::AsyncReadExt};
|
||||||
|
|
||||||
use crate::types::{message::{Message, Photo}, participant::Participant, project::ProjectRaw, user::User};
|
use crate::{services::service::Service, types::{participant::Participant, project::ProjectRaw, user::User}};
|
||||||
|
|
||||||
use super::service::Service;
|
use super::types::{InstagramMessage, InstagramParticipant};
|
||||||
|
|
||||||
fn decode_str(input: &String) -> String {
|
pub fn decode_str(input: &String) -> String {
|
||||||
let vec = ISO_8859_1.encode(input.as_str(), EncoderTrap::Strict).unwrap();
|
let vec = ISO_8859_1.encode(input.as_str(), EncoderTrap::Strict).unwrap();
|
||||||
String::from_utf8(vec).unwrap()
|
String::from_utf8(vec).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct InstagramMessage {
|
|
||||||
sender_name: String,
|
|
||||||
timestamp_ms: usize,
|
|
||||||
content: Option<String>,
|
|
||||||
photos: Option<Vec<InstagramPhoto>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstagramMessage {
|
|
||||||
pub fn conv(&self, media_index: &mut usize, project_path: PathBuf, user_map: &HashMap<Participant, User>) -> Message {
|
|
||||||
let text_content = if let Some(content) = &self.content {
|
|
||||||
Some(decode_str(content))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let photos: Vec<Photo> = if let Some(photos_list) = &self.photos {
|
|
||||||
photos_list.iter().map(|photo| photo.conv(&text_content, media_index, project_path.clone())).collect()
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
let participant = Participant {
|
|
||||||
name: decode_str(&self.sender_name)
|
|
||||||
};
|
|
||||||
|
|
||||||
Message {
|
|
||||||
sender_name: user_map.get(&participant).unwrap().name.clone(),
|
|
||||||
text_content,
|
|
||||||
timestamp: self.timestamp_ms,
|
|
||||||
photos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct InstagramPhoto {
|
|
||||||
uri: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstagramPhoto {
|
|
||||||
pub fn conv(&self, text_content: &Option<String>, media_index: &mut usize, mut original_path: PathBuf) -> Photo {
|
|
||||||
*media_index += 1;
|
|
||||||
let img_path = (*media_index - 1).to_string();
|
|
||||||
|
|
||||||
for _ in 0..4 {
|
|
||||||
if !original_path.pop() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
original_path.push(self.uri.clone());
|
|
||||||
|
|
||||||
Photo {
|
|
||||||
text_content: text_content.clone(),
|
|
||||||
img_path,
|
|
||||||
original_img_path: Some(original_path.display().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct InstagramParticipant {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct InstagramDataJson {
|
struct InstagramDataJson {
|
||||||
messages: Vec<InstagramMessage>,
|
messages: Vec<InstagramMessage>,
|
||||||
@ -212,7 +146,7 @@ impl Service for Instagram {
|
|||||||
let moved = raw_project.project.push_msg(&msg);
|
let moved = raw_project.project.push_msg(&msg);
|
||||||
|
|
||||||
if moved {
|
if moved {
|
||||||
// Move the file to the media folder
|
// Move media files to the media folder
|
||||||
for photo in msg.photos.clone() {
|
for photo in msg.photos.clone() {
|
||||||
let original_path = photo.original_img_path.unwrap().clone();
|
let original_path = photo.original_img_path.unwrap().clone();
|
||||||
let path = format!("media/{}", photo.img_path);
|
let path = format!("media/{}", photo.img_path);
|
||||||
@ -220,6 +154,20 @@ impl Service for Instagram {
|
|||||||
media_files.push(photovalues);
|
media_files.push(photovalues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for video in msg.videos.clone() {
|
||||||
|
let original_path = video.original_vid_path.unwrap().clone();
|
||||||
|
let path = format!("media/{}", video.vid_path);
|
||||||
|
let photovalues = (path, original_path);
|
||||||
|
media_files.push(photovalues);
|
||||||
|
}
|
||||||
|
|
||||||
|
for audio in msg.audio.clone() {
|
||||||
|
let original_path = audio.original_audio_path.unwrap().clone();
|
||||||
|
let path = format!("media/{}", audio.audio_path);
|
||||||
|
let photovalues = (path, original_path);
|
||||||
|
media_files.push(photovalues);
|
||||||
|
}
|
||||||
|
|
||||||
// Update the highest media index of the project if the message was merged
|
// Update the highest media index of the project if the message was merged
|
||||||
raw_project.project.media_index = media_index_clone;
|
raw_project.project.media_index = media_index_clone;
|
||||||
}
|
}
|
2
src/services/instagram/mod.rs
Normal file
2
src/services/instagram/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod types;
|
||||||
|
pub mod instagram;
|
168
src/services/instagram/types.rs
Normal file
168
src/services/instagram/types.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::types::{message::{Audio, Message, Photo, Reaction, Video}, participant::Participant, user::User};
|
||||||
|
use super::instagram::decode_str;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct InstagramParticipant {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct InstagramMessage {
|
||||||
|
sender_name: String,
|
||||||
|
timestamp_ms: usize,
|
||||||
|
content: Option<String>,
|
||||||
|
photos: Option<Vec<InstagramPhoto>>,
|
||||||
|
videos: Option<Vec<InstagramVideo>>,
|
||||||
|
audio_files: Option<Vec<InstagramAudio>>,
|
||||||
|
reactions: Option<Vec<InstagramReaction>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstagramMessage {
|
||||||
|
pub fn conv(&self, media_index: &mut usize, project_path: PathBuf, user_map: &HashMap<Participant, User>) -> Message {
|
||||||
|
let text_content = if let Some(content) = &self.content {
|
||||||
|
Some(decode_str(content))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let photos: Vec<Photo> = if let Some(photos_list) = &self.photos {
|
||||||
|
photos_list.iter().map(|photo| photo.conv(&text_content, media_index, project_path.clone())).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let videos: Vec<Video> = if let Some(videos_list) = &self.videos {
|
||||||
|
videos_list.iter().map(|video| video.conv(&text_content, media_index, project_path.clone())).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let audio: Vec<Audio> = if let Some(audio_list) = &self.audio_files {
|
||||||
|
audio_list.iter().map(|audio| audio.conv(&text_content, media_index, project_path.clone())).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let reactions: Vec<Reaction> = if let Some(reaction_list) = &self.reactions {
|
||||||
|
reaction_list.iter().map(|reaction| reaction.conv()).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let participant = Participant {
|
||||||
|
name: decode_str(&self.sender_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sender = user_map.get(&participant);
|
||||||
|
let sender_name = if let Some(sender_value) = sender {
|
||||||
|
sender_value.name.clone()
|
||||||
|
} else {
|
||||||
|
String::from("Unknown user")
|
||||||
|
};
|
||||||
|
|
||||||
|
Message {
|
||||||
|
sender_name,
|
||||||
|
text_content,
|
||||||
|
timestamp: self.timestamp_ms,
|
||||||
|
service: crate::types::message::Service::Instagram,
|
||||||
|
photos,
|
||||||
|
videos,
|
||||||
|
audio,
|
||||||
|
reactions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct InstagramReaction {
|
||||||
|
pub reaction: String,
|
||||||
|
pub actor: String,
|
||||||
|
pub timestamp: Option<usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstagramReaction {
|
||||||
|
pub fn conv(&self) -> Reaction {
|
||||||
|
Reaction {
|
||||||
|
actor_name: decode_str(&self.actor),
|
||||||
|
content: decode_str(&self.reaction),
|
||||||
|
timestamp: self.timestamp.unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media files
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct InstagramPhoto {
|
||||||
|
uri: String
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct InstagramVideo {
|
||||||
|
uri: String
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct InstagramAudio {
|
||||||
|
uri: String
|
||||||
|
}
|
||||||
|
impl InstagramPhoto {
|
||||||
|
pub fn conv(&self, text_content: &Option<String>, media_index: &mut usize, mut original_path: PathBuf) -> Photo {
|
||||||
|
*media_index += 1;
|
||||||
|
let img_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
for _ in 0..4 {
|
||||||
|
if !original_path.pop() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Photo {
|
||||||
|
text_content: text_content.clone(),
|
||||||
|
img_path,
|
||||||
|
original_img_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstagramVideo {
|
||||||
|
pub fn conv(&self, text_content: &Option<String>, media_index: &mut usize, mut original_path: PathBuf) -> Video {
|
||||||
|
*media_index += 1;
|
||||||
|
let vid_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
for _ in 0..4 {
|
||||||
|
if !original_path.pop() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Video {
|
||||||
|
text_content: text_content.clone(),
|
||||||
|
vid_path,
|
||||||
|
original_vid_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstagramAudio {
|
||||||
|
pub fn conv(&self, text_content: &Option<String>, media_index: &mut usize, mut original_path: PathBuf) -> Audio {
|
||||||
|
*media_index += 1;
|
||||||
|
let audio_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
for _ in 0..4 {
|
||||||
|
if !original_path.pop() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Audio {
|
||||||
|
text_content: text_content.clone(),
|
||||||
|
audio_path,
|
||||||
|
original_audio_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,4 @@
|
|||||||
pub mod service;
|
pub mod service;
|
||||||
|
|
||||||
pub mod instagram;
|
pub mod instagram;
|
||||||
|
pub mod whatsapp;
|
||||||
|
2
src/services/whatsapp/mod.rs
Normal file
2
src/services/whatsapp/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod whatsapp;
|
||||||
|
pub mod types;
|
125
src/services/whatsapp/types.rs
Normal file
125
src/services/whatsapp/types.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::types::{message::{Audio, Message, Photo, Video}, participant::Participant, user::User};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WhatsAppMessage {
|
||||||
|
pub sender: String,
|
||||||
|
pub epoch: usize,
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub photos: Vec<WhatsAppPhoto>,
|
||||||
|
pub videos: Vec<WhatsAppVideo>,
|
||||||
|
pub audio: Option<WhatsAppAudio>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhatsAppMessage {
|
||||||
|
pub fn conv(&self, media_index: &mut usize, project_path: PathBuf, user_map: &HashMap<Participant, User>) -> Message {
|
||||||
|
let photos: Vec<Photo> =
|
||||||
|
self.photos.iter().map(|photo| photo.conv(media_index, project_path.clone())).collect();
|
||||||
|
let videos: Vec<Video> =
|
||||||
|
self.videos.iter().map(|video| video.conv(media_index, project_path.clone())).collect();
|
||||||
|
let audio: Vec<Audio> = if let Some(audio_f) = &self.audio {
|
||||||
|
vec![audio_f.conv(media_index, project_path.clone())]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
let participant = Participant {
|
||||||
|
name: self.sender.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sender = user_map.get(&participant);
|
||||||
|
let sender_name = if let Some(sender_value) = sender {
|
||||||
|
sender_value.name.clone()
|
||||||
|
} else {
|
||||||
|
String::from("Unknown user")
|
||||||
|
};
|
||||||
|
|
||||||
|
Message {
|
||||||
|
sender_name,
|
||||||
|
text_content: self.content.clone(),
|
||||||
|
timestamp: self.epoch,
|
||||||
|
service: crate::types::message::Service::WhatsApp,
|
||||||
|
photos,
|
||||||
|
videos,
|
||||||
|
audio,
|
||||||
|
reactions: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Media files
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WhatsAppPhoto {
|
||||||
|
pub uri: String,
|
||||||
|
pub text_content: String,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WhatsAppVideo {
|
||||||
|
pub uri: String,
|
||||||
|
pub text_content: String,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WhatsAppAudio {
|
||||||
|
pub uri: String,
|
||||||
|
pub text_content: String,
|
||||||
|
}
|
||||||
|
impl WhatsAppPhoto {
|
||||||
|
pub fn conv(&self, media_index: &mut usize, mut original_path: PathBuf) -> Photo {
|
||||||
|
*media_index += 1;
|
||||||
|
let img_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Photo {
|
||||||
|
text_content: Some(self.text_content.clone()),
|
||||||
|
img_path,
|
||||||
|
original_img_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WhatsAppVideo {
|
||||||
|
pub fn conv(&self, media_index: &mut usize, mut original_path: PathBuf) -> Video {
|
||||||
|
*media_index += 1;
|
||||||
|
let vid_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Video {
|
||||||
|
text_content: Some(self.text_content.clone()),
|
||||||
|
vid_path,
|
||||||
|
original_vid_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WhatsAppAudio {
|
||||||
|
pub fn conv(&self, media_index: &mut usize, mut original_path: PathBuf) -> Audio {
|
||||||
|
*media_index += 1;
|
||||||
|
let audio_path = (*media_index - 1).to_string();
|
||||||
|
|
||||||
|
original_path.push(self.uri.clone());
|
||||||
|
|
||||||
|
Audio {
|
||||||
|
text_content: Some(self.text_content.clone()),
|
||||||
|
audio_path,
|
||||||
|
original_audio_path: Some(original_path.display().to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub struct WhatsAppParticipant {
|
||||||
|
pub name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhatsAppParticipant {
|
||||||
|
pub fn conv(&self) -> Participant {
|
||||||
|
Participant {
|
||||||
|
name: self.name.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
232
src/services/whatsapp/whatsapp.rs
Normal file
232
src/services/whatsapp/whatsapp.rs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
use std::{collections::{HashMap, HashSet}, fs, io, path::PathBuf, process::exit};
|
||||||
|
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use dialoguer::Input;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use regex::Regex;
|
||||||
|
use tokio::{fs::File, io::AsyncReadExt};
|
||||||
|
|
||||||
|
use crate::{services::service::Service, types::{participant::Participant, project::ProjectRaw, user::User}};
|
||||||
|
|
||||||
|
use super::types::{WhatsAppAudio, WhatsAppMessage, WhatsAppParticipant, WhatsAppPhoto, WhatsAppVideo};
|
||||||
|
|
||||||
|
pub fn parse_messages(input: &str) -> Vec<WhatsAppMessage> {
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
let re = Regex::new(r"^(\d{2}/\d{2}/\d{2}), (\d{2}:\d{2}) - (.*?): (.*)").unwrap();
|
||||||
|
let mut current_message: Option<WhatsAppMessage> = None;
|
||||||
|
|
||||||
|
for line in input.lines() {
|
||||||
|
if let Some(captures) = re.captures(line) {
|
||||||
|
let date = captures.get(1).unwrap().as_str();
|
||||||
|
let time = captures.get(2).unwrap().as_str();
|
||||||
|
let sender = captures.get(3).unwrap().as_str().to_string();
|
||||||
|
let content = captures.get(4).unwrap().as_str();
|
||||||
|
|
||||||
|
// Parse date as DD/MM/YY and convert to epoch
|
||||||
|
let datetime_str = format!("{} {}", date, time); // DD/MM/YY HH:MM
|
||||||
|
let datetime = NaiveDateTime::parse_from_str(&datetime_str, "%d/%m/%y %H:%M").unwrap();
|
||||||
|
let epoch = datetime.and_utc().timestamp() as usize;
|
||||||
|
|
||||||
|
// Save the previous message if there is one
|
||||||
|
if let Some(msg) = current_message.take() {
|
||||||
|
messages.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle media messages
|
||||||
|
if content.starts_with("IMG") || content.starts_with("STK") {
|
||||||
|
current_message = Some(WhatsAppMessage {
|
||||||
|
sender,
|
||||||
|
epoch,
|
||||||
|
content: None,
|
||||||
|
photos: vec![WhatsAppPhoto {
|
||||||
|
uri: content.split(' ').next().unwrap().to_string(),
|
||||||
|
text_content: String::new(),
|
||||||
|
}],
|
||||||
|
videos: vec![],
|
||||||
|
audio: None,
|
||||||
|
});
|
||||||
|
} else if content.starts_with("VID") {
|
||||||
|
current_message = Some(WhatsAppMessage {
|
||||||
|
sender,
|
||||||
|
epoch,
|
||||||
|
content: None,
|
||||||
|
photos: vec![],
|
||||||
|
videos: vec![WhatsAppVideo {
|
||||||
|
uri: content.split(' ').next().unwrap().to_string(),
|
||||||
|
text_content: String::new(),
|
||||||
|
}],
|
||||||
|
audio: None,
|
||||||
|
});
|
||||||
|
} else if content.starts_with("PTT") {
|
||||||
|
messages.push(WhatsAppMessage {
|
||||||
|
sender,
|
||||||
|
epoch,
|
||||||
|
content: None,
|
||||||
|
photos: vec![],
|
||||||
|
videos: vec![],
|
||||||
|
audio: Some(WhatsAppAudio {
|
||||||
|
uri: content.split(' ').next().unwrap().to_string(),
|
||||||
|
text_content: String::new(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Regular message
|
||||||
|
current_message = Some(WhatsAppMessage {
|
||||||
|
sender,
|
||||||
|
epoch,
|
||||||
|
content: Some(content.to_string()),
|
||||||
|
photos: vec![],
|
||||||
|
videos: vec![],
|
||||||
|
audio: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if let Some(msg) = current_message.as_mut() {
|
||||||
|
// Handle continuations or appending to media
|
||||||
|
if let Some(photo) = msg.photos.last_mut() {
|
||||||
|
photo.text_content.push('\n');
|
||||||
|
photo.text_content.push_str(line);
|
||||||
|
} else if let Some(video) = msg.videos.last_mut() {
|
||||||
|
video.text_content.push('\n');
|
||||||
|
video.text_content.push_str(line);
|
||||||
|
} else if let Some(content) = msg.content.as_mut() {
|
||||||
|
content.push('\n');
|
||||||
|
content.push_str(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last message if any
|
||||||
|
if let Some(msg) = current_message {
|
||||||
|
messages.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
messages
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct WhatsAppData {
|
||||||
|
pub project_path: String,
|
||||||
|
pub participants: Vec<WhatsAppParticipant>,
|
||||||
|
pub messages: Vec<WhatsAppMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WhatsApp {
|
||||||
|
pub data: Option<WhatsAppData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Service for WhatsApp {
|
||||||
|
fn get_name(&self) -> &'static str {
|
||||||
|
"WhatsApp"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&self) -> Box<dyn Service> {
|
||||||
|
let export_path: String = Input::new()
|
||||||
|
.with_prompt("Enter path for your WhatsApp export. (extracted zip file)")
|
||||||
|
.interact()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Box::new(WhatsApp {
|
||||||
|
data: Some(WhatsAppData {
|
||||||
|
project_path: export_path,
|
||||||
|
participants: vec![],
|
||||||
|
messages: vec![],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load(&mut self) -> io::Result<()> {
|
||||||
|
let data = self.data.as_mut().unwrap();
|
||||||
|
let project_path = &data.project_path;
|
||||||
|
|
||||||
|
let paths: Vec<PathBuf> = fs::read_dir(project_path).unwrap_or_else(|_| {
|
||||||
|
eprintln!("Folder not found!");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| {
|
||||||
|
if let Some(extension) = entry.path().extension() {
|
||||||
|
entry.path().is_file() && extension == "txt"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|entry| entry.path()).collect();
|
||||||
|
|
||||||
|
if paths.len() != 1 {
|
||||||
|
eprintln!("The chat file was not found in the WhatsApp export");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_path = paths.get(0).unwrap();
|
||||||
|
|
||||||
|
let mut file = File::open(file_path).await?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents).await?;
|
||||||
|
|
||||||
|
let messages = parse_messages(&contents);
|
||||||
|
|
||||||
|
let mut participants: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
for message in &messages {
|
||||||
|
participants.insert(message.sender.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
data.participants = participants.into_iter().map(|p| {
|
||||||
|
WhatsAppParticipant {
|
||||||
|
name: p
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
data.messages = messages;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_participants(&self) -> Vec<Participant> {
|
||||||
|
self.data.as_ref().unwrap().participants.iter().map(|part| part.conv()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn merge_messages(&self, raw_project: &mut ProjectRaw, user_map: &HashMap<Participant, User>) {
|
||||||
|
let data = self.data.as_ref().unwrap();
|
||||||
|
let path = PathBuf::from(data.project_path.clone());
|
||||||
|
let mut media_files: Vec<(String, String)> = vec![];
|
||||||
|
|
||||||
|
for message in &data.messages {
|
||||||
|
let mut media_index_clone = raw_project.project.media_index.clone();
|
||||||
|
let msg = message.conv(&mut media_index_clone, path.clone(), user_map);
|
||||||
|
|
||||||
|
let moved = raw_project.project.push_msg(&msg);
|
||||||
|
|
||||||
|
if moved {
|
||||||
|
// Move media files to the media folder
|
||||||
|
for photo in msg.photos.clone() {
|
||||||
|
let original_path = photo.original_img_path.unwrap().clone();
|
||||||
|
let path = format!("media/{}", photo.img_path);
|
||||||
|
let photovalues = (path, original_path);
|
||||||
|
media_files.push(photovalues);
|
||||||
|
}
|
||||||
|
|
||||||
|
for video in msg.videos.clone() {
|
||||||
|
let original_path = video.original_vid_path.unwrap().clone();
|
||||||
|
let path = format!("media/{}", video.vid_path);
|
||||||
|
let photovalues = (path, original_path);
|
||||||
|
media_files.push(photovalues);
|
||||||
|
}
|
||||||
|
|
||||||
|
for audio in msg.audio.clone() {
|
||||||
|
let original_path = audio.original_audio_path.unwrap().clone();
|
||||||
|
let path = format!("media/{}", audio.audio_path);
|
||||||
|
let photovalues = (path, original_path);
|
||||||
|
media_files.push(photovalues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the highest media index of the project if the message was merged
|
||||||
|
raw_project.project.media_index = media_index_clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = raw_project.save_media_files(media_files).await;
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,24 @@ pub struct Message {
|
|||||||
pub sender_name: String,
|
pub sender_name: String,
|
||||||
pub timestamp: usize,
|
pub timestamp: usize,
|
||||||
pub text_content: Option<String>,
|
pub text_content: Option<String>,
|
||||||
pub photos: Vec<Photo>
|
pub photos: Vec<Photo>,
|
||||||
|
pub videos: Vec<Video>,
|
||||||
|
pub audio: Vec<Audio>,
|
||||||
|
pub service: Service,
|
||||||
|
pub reactions: Vec<Reaction>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Reaction {
|
||||||
|
pub actor_name: String,
|
||||||
|
pub content: String,
|
||||||
|
pub timestamp: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum Service {
|
||||||
|
Instagram,
|
||||||
|
WhatsApp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@ -16,3 +33,21 @@ pub struct Photo {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub original_img_path: Option<String>,
|
pub original_img_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Video {
|
||||||
|
pub text_content: Option<String>,
|
||||||
|
pub vid_path: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub original_vid_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Audio {
|
||||||
|
pub text_content: Option<String>,
|
||||||
|
pub audio_path: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub original_audio_path: Option<String>,
|
||||||
|
}
|
||||||
|
@ -14,12 +14,25 @@ pub struct Project {
|
|||||||
pub media_index: usize,
|
pub media_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ProjectJson {
|
||||||
|
pub users: Vec<User>,
|
||||||
|
pub messages: Vec<Message>
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ProjectRaw {
|
pub struct ProjectRaw {
|
||||||
pub project: Project,
|
pub project: Project,
|
||||||
pub zip: ZipArchive<Cursor<Vec<u8>>>
|
pub zip: ZipArchive<Cursor<Vec<u8>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
pub fn to_projectjson(&self) -> ProjectJson {
|
||||||
|
ProjectJson {
|
||||||
|
users: self.users.clone(),
|
||||||
|
messages: self.messages.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn load(pathstr: String) -> io::Result<ProjectRaw> {
|
pub async fn load(pathstr: String) -> io::Result<ProjectRaw> {
|
||||||
let path = PathBuf::from(pathstr);
|
let path = PathBuf::from(pathstr);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user