diff --git a/Cargo.toml b/Cargo.toml index 873c57f..6e7f625 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ edition = "2021" eframe = "0.26.2" futures = "0.3.30" image = "0.24.9" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" chrono = "0.4" serenity = { version = "0.9.0-rc.2", default-features = false, features = ["client", "gateway", "model", "builder", "rustls_backend", "cache", "http"] } tokio = {features = ["macros"], version = "0.2"} +homedir = "0.2.1" diff --git a/jiji.project b/jiji.project index 0e2d144..08c2bc6 100644 --- a/jiji.project +++ b/jiji.project @@ -1 +1 @@ -{"categories":[{"name":"to do","content":[{"name":"ability to change token","description":"use a config file to store token so that \n\n1- it is away from github\n\n2- it is configurable if need be","id":1},{"name":"better ui error display","description":"handle the error packet for better display","id":1},{"name":"clean up bot code","description":"try to remove unnecessary code\n\nunindent\n\ngive sender to function ?","id":2},{"name":"proper links","description":"when there is a link, ability to click it","id":3},{"name":"trayable ?","description":"// Hello there","id":5},{"name":"handle unknown channel better","description":"when receiving a message from a not yet scanned guild, create the channel and put the message\n\nallow scanning if guild selected\n\ndo not add duplicate channel","id":1},{"name":"guild unread ?","description":"// Hello there","id":2}]},{"name":"in progress","content":[{"name":"notifications !!!","description":"// Hello there","id":4}]},{"name":"done","content":[{"name":"run discord bot","description":"make it so the bot is running","id":1},{"name":"fixed token in github","description":"// Hello there","id":1},{"name":"fetch previous messages","description":"// Hello there","id":4},{"name":"ability to write messages","description":"// Hello there","id":5},{"name":"get incoming messages","description":"read","id":2},{"name":"unread system","description":"add a * when a channel just received a message","id":1}]},{"name":"bugs","content":[]},{"name":"+","content":[]}]} \ No newline at end of file +{"categories":[{"name":"to do","content":[{"name":"ability to change token","description":"use a config file to store token so that \n\n1- it is away from github\n\n2- it is configurable if need be","id":1},{"name":"better ui error display","description":"handle the error packet for better display","id":1},{"name":"clean up bot code","description":"try to remove unnecessary code\n\nunindent\n\ngive sender to function ?","id":2},{"name":"proper links","description":"when there is a link, ability to click it","id":3},{"name":"trayable ?","description":"// Hello there","id":5},{"name":"handle unknown channel better","description":"when receiving a message from a not yet scanned guild, create the channel and put the message\n\nallow scanning if guild selected\n\ndo not add duplicate channel","id":1},{"name":"guild unread ?","description":"// Hello there","id":2},{"name":"timestamps","description":"// Hello there","id":1},{"name":"new message marker","description":"perma if too complicated to detect read","id":2},{"name":"remember channel id for dm","description":"and put in config file\n\nmaybe load message ? dm first ?","id":3}]},{"name":"in progress","content":[{"name":"notifications !!!","description":"// Hello there","id":4}]},{"name":"done","content":[{"name":"run discord bot","description":"make it so the bot is running","id":1},{"name":"fixed token in github","description":"// Hello there","id":1},{"name":"fetch previous messages","description":"// Hello there","id":4},{"name":"ability to write messages","description":"// Hello there","id":5},{"name":"get incoming messages","description":"read","id":2},{"name":"unread system","description":"add a * when a channel just received a message","id":1}]},{"name":"bugs","content":[]},{"name":"+","content":[]}]} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 49f7606..015a4ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ use eframe::egui; -use image::GenericImageView; -use std::{error::Error, sync::Arc, sync::mpsc, thread, time}; +use std::{sync::Arc, sync::mpsc, thread, time}; use tokio::runtime::Runtime; use std::sync::Mutex; +use std::path::PathBuf; +use std::collections::HashMap; +use homedir::get_my_home; mod bot; mod postman; mod discord_structure; +mod state; const MAX_FPS: f32 = 30.0; const RUNNING_REQUEST_REFRESH_DELAY: f32 = 0.2; @@ -27,7 +30,7 @@ fn main() { } fn gui(sender: mpsc::Sender, receiver: mpsc::Receiver) { - let icon_data = load_icon().unwrap_or_default(); + let icon_data = state::load_icon().unwrap_or_default(); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() @@ -43,6 +46,7 @@ struct Jiji { next_frame: time::Instant, sender: mpsc::Sender, receiver: mpsc::Receiver, + bot_token: String, guilds: Vec, selected_guild: Option, selected_channel: Option, @@ -54,17 +58,20 @@ struct Jiji { impl Jiji { fn new(sender: mpsc::Sender, receiver: mpsc::Receiver) -> Self { + let app_state = state::load_state(&save_path()); + Self { next_frame: time::Instant::now(), sender, receiver, + bot_token: app_state.bot_token.clone(), guilds: vec![], selected_guild: None, selected_channel: None, time_watch: 0.0, pending_bot_requests: 0, current_message: "".into(), - channels_to_notify: vec![], + channels_to_notify: app_state.channels_to_notify.clone(), } } } @@ -88,7 +95,17 @@ impl eframe::App for Jiji { if self.guilds[i].id != channel.guild_id { continue } - self.guilds[i].add_channel(channel.clone()); + + let mut discord_channel = channel.clone(); // finish if with variable clone of channel to update with notify + + if let Some(index) = self.channels_to_notify.iter().position(|x| x == &channel.id) { + discord_channel.notify = true; + self.channels_to_notify.remove(index); + } + + self.guilds[i].add_channel(discord_channel.clone()); + + } } postman::Packet::Message(message) => { @@ -176,7 +193,7 @@ impl eframe::App for Jiji { } fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { - //self.runtime.shutdown_background(); + self.save_state(); } } @@ -337,24 +354,39 @@ impl Jiji { }); }); } -} - -pub fn load_icon() -> Result> { - let (icon_rgba, icon_width, icon_height) = { - let icon = include_bytes!("../assets/icon.png"); - let image = image::load_from_memory(icon)?; - let rgba = image.clone().into_rgba8().to_vec(); - let (width, height) = image.dimensions(); - (rgba, width, height) - }; - - Ok(egui::IconData { - rgba: icon_rgba, - width: icon_width, - height: icon_height, - }) + + pub fn save_state(&self) { + let mut channels_to_notify = self.channels_to_notify.clone(); + + for g in 0..self.guilds.len() { + for c in 0..self.guilds[g].channels.len() { + if !self.guilds[g].channels[c].notify { + continue + } + channels_to_notify.push(self.guilds[g].channels[c].id.clone()); + } + } + + let app_state = state::AppState { + bot_token: self.bot_token.clone(), + channels_to_notify: channels_to_notify, + dm_channels: HashMap::new(), + }; + let _ = state::save_state(&app_state, save_path().as_path()); + } } pub fn hex_str_to_color(hex_str: &str) -> egui::Color32 { egui::Color32::from_hex(hex_str).unwrap_or_else(|_| egui::Color32::WHITE) -} \ No newline at end of file +} + +pub fn save_path() -> PathBuf { + get_my_home() + .unwrap() + .unwrap() + .as_path() + .join(".jiji") + .join("save.json") + .to_path_buf() +} + diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..25003d4 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,75 @@ +use eframe::egui; +use image::GenericImageView; +use std::{ + error::Error, + fs, + fs::{read_to_string, OpenOptions}, + io::Write, + path::Path, +}; +use serde::Serialize; +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct AppState { + pub bot_token: String, + pub channels_to_notify: Vec, + pub dm_channels: HashMap, +} + +impl Default for AppState { + fn default() -> Self { + Self { + bot_token: "".to_string(), + channels_to_notify: vec![], + dm_channels: HashMap::new() + } + } +} + +pub fn save_state(state: &AppState, file_path: &Path) -> Result<(), std::io::Error> { + let serialized_state = serde_json::to_string(state)?; + + if let Some(parent_dir) = file_path.parent() { + fs::create_dir_all(parent_dir)?; + } + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(file_path)?; + + file.write_all(serialized_state.as_bytes())?; + + println!("state : Saved state at {}", file_path.display()); + + Ok(()) +} + +pub fn load_state(file_path: &Path) -> AppState { + if let Ok(serialized_state) = read_to_string(file_path) { + if let Ok(app_state) = serde_json::from_str(&serialized_state) { + return app_state + } + } + println!("state : failed to load state"); + AppState::default() +} + +pub fn load_icon() -> Result> { + let (icon_rgba, icon_width, icon_height) = { + let icon = include_bytes!("../assets/icon.png"); + let image = image::load_from_memory(icon)?; + let rgba = image.clone().into_rgba8().to_vec(); + let (width, height) = image.dimensions(); + (rgba, width, height) + }; + + Ok(egui::IconData { + rgba: icon_rgba, + width: icon_width, + height: icon_height, + }) +}