use cursive::{views::TextArea, Cursive}; use utils::{hash::Hashable as _, serialize::Serialize as _}; use crate::{get_appdata, message::Message, ui, Appdata}; // TODO (low): Create proper user objects. Related: add a way to verify a user. pub fn on_user_click(siv: &mut Cursive, user: &str) { let language = get_appdata(siv).language; ui::alert(siv, ui::Labels::User.localize(language), user.into()); } pub fn on_message_click(siv: &mut Cursive, message_id: &str) { let appdata = get_appdata(siv); let message = appdata.messages.get(message_id); if let Some(message) = message { ui::alert( siv, ui::Labels::Message.localize(appdata.language), format!( "Time: {}\nChannel: {}\nSender: {}\nContent: {}", message.time, message.channel, message.sender, message.content ), ); } } pub fn on_input_submit(siv: &mut Cursive, input_field_id: &str) { let text = siv .call_on_name(input_field_id, |view: &mut TextArea| { view.get_content().to_string() }) .expect("Failed to retrieve input field content."); let appdata = get_appdata(siv); let language = appdata.language; let channel = appdata.current_channel; let message = Message::new(&appdata.username, &text, &channel); if message.is_valid() { // NOTE: // If an error was shown during send, it's likely that loading will fail too // so we don't need to show it again let mut error_dialogue_shown = false; match send_message(siv, message) { Ok(_) => clear_input_field(siv), Err(e) => { error_dialogue_shown = true; ui::error(siv, ui::Labels::FailedToSendMessage(e).localize(language)) } }; let res = load_messages(siv); if let Err(e) = res { if !error_dialogue_shown { ui::error(siv, ui::Labels::FailedToLoadMessages(e).localize(language)); } } } else if !text.is_empty() { // Invalid message ui::error(siv, ui::Labels::InvalidMessage.localize(language)); } ui::visual_update(siv); } pub fn clear_input_field(siv: &mut Cursive) { siv.call_on_name(ui::INPUT_FIELD_ID, |view: &mut TextArea| { view.set_content(""); }) .expect("Failed to clear input field."); } type StatusCode = u16; pub enum NetworkError { #[allow(dead_code)] ReqwestError(reqwest::Error), StatusCodeError(StatusCode), InvalidUrl, } fn fix_url(url: &str) -> Result { if url.is_empty() { return Err(NetworkError::InvalidUrl); } let mut url = url.to_string(); if !url.starts_with("http://") && !url.starts_with("https://") { url = format!("http://{}", url); } if !url.ends_with('/') { url.push('/'); } Ok(url) } pub fn send_message(siv: &mut Cursive, message: Message) -> Result<(), NetworkError> { let url = fix_url(&get_appdata(siv).api_endpoint)?; let str = utils::binary::bin2hex(message.serialize_checked()); let resp = reqwest::blocking::Client::new() .post(format!("{}", url)) .body(str) .send() .map_err(|e| NetworkError::ReqwestError(e))?; if resp.status().is_success() { Ok(()) } else { Err(NetworkError::StatusCodeError(resp.status().as_u16())) } } pub fn load_messages(siv: &mut Cursive) -> Result<(), NetworkError> { let appdata = get_appdata(siv); let url = fix_url(&appdata.api_endpoint)?; let resp = reqwest::blocking::Client::new() .get(url) .send() .map_err(|e| NetworkError::ReqwestError(e))?; if resp.status() != reqwest::StatusCode::OK { return Err(NetworkError::StatusCodeError(resp.status().as_u16())); } let bytes = resp.bytes().map_err(|e| NetworkError::ReqwestError(e))?; let contents = String::from_utf8_lossy(&bytes); let mut messages = vec![]; for message_ser_hex in contents.split(",") { let Some(message_ser_bin) = utils::binary::hex2bin(message_ser_hex) else { continue; }; let Ok(message) = Message::deserialize_checked(message_ser_bin) else { continue; }; if message.is_valid() { messages.push(message); } } siv.with_user_data(|appdata: &mut Appdata| { for message in messages { appdata.messages.insert(message.hash().to_string(), message); } }); Ok(()) }