actions.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. use std::time::Duration;
  2. use client_shared::{API_CONNECTION_TIMEOUT, message::Message, persistence::Appdata, utils::fix_url};
  3. use cursive::{views::TextArea, Cursive};
  4. use utils::{binary::checksum, hash::Hashable as _, serialize::Serialize as _};
  5. use crate::{get_appdata, ui};
  6. // TODO (low): Create proper user objects. Related: add a way to verify a user.
  7. pub fn on_user_click(siv: &mut Cursive, user: &str) {
  8. let language = get_appdata(siv).persistent_data.language;
  9. ui::alert(siv, ui::Labels::User.localize(language), user.into());
  10. }
  11. pub fn on_message_click(siv: &mut Cursive, message_id: &str) {
  12. let appdata = get_appdata(siv);
  13. let language = appdata.persistent_data.language;
  14. let message = appdata.messages.get(message_id);
  15. if let Some(message) = message {
  16. ui::copyable(
  17. siv,
  18. ui::Labels::Message.localize(appdata.persistent_data.language),
  19. format!(
  20. "{}: {}\n{}: {}\n{}: {}\n{}: {}",
  21. ui::Labels::Time.localize(language), message.time,
  22. ui::Labels::Channel.localize(language), message.channel,
  23. ui::Labels::Sender.localize(language), message.sender,
  24. ui::Labels::Content.localize(language), message.content
  25. ),
  26. );
  27. }
  28. }
  29. // TODO: Sending in progress pop-up for slow connections. Currently just hangs the application.
  30. pub fn on_input_submit(siv: &mut Cursive, input_field_id: &str) {
  31. let text = siv
  32. .call_on_name(input_field_id, |view: &mut TextArea| {
  33. view.get_content().to_string()
  34. })
  35. .expect("Failed to retrieve input field content.");
  36. let appdata = get_appdata(siv);
  37. let language = appdata.persistent_data.language;
  38. let channel = appdata.persistent_data.current_channel;
  39. let message = Message::new(&appdata.persistent_data.username, &text, &channel);
  40. if message.is_valid() {
  41. // NOTE:
  42. // If an error was shown during send, it's likely that loading will fail too
  43. // so we don't need to show it again
  44. let mut error_dialogue_shown = false;
  45. match send_message(siv, message) {
  46. Ok(_) => clear_input_field(siv),
  47. Err(e) => {
  48. error_dialogue_shown = true;
  49. ui::error(siv, ui::Labels::FailedToSendMessage(e).localize(language))
  50. }
  51. };
  52. let res = load_messages(siv);
  53. if let Err(e) = res {
  54. if !error_dialogue_shown {
  55. ui::error(siv, ui::Labels::FailedToLoadMessages(e).localize(language));
  56. }
  57. } else {
  58. ui::alert(
  59. siv,
  60. ui::Labels::Success.localize(language),
  61. ui::Labels::SuccessfullySentMessage.localize(language),
  62. );
  63. }
  64. } else if !text.is_empty() {
  65. // Invalid message
  66. ui::error(siv, ui::Labels::InvalidMessage.localize(language));
  67. }
  68. ui::visual_update(siv);
  69. }
  70. pub fn clear_input_field(siv: &mut Cursive) {
  71. siv.call_on_name(ui::INPUT_FIELD_ID, |view: &mut TextArea| {
  72. view.set_content("");
  73. })
  74. .expect("Failed to clear input field.");
  75. }
  76. type StatusCode = u16;
  77. #[allow(dead_code)]
  78. pub enum NetworkError {
  79. ReqwestError(reqwest::Error),
  80. StatusCodeError(StatusCode),
  81. EncryptionError(utils::aes::AesError),
  82. UrlError(client_shared::utils::UrlError),
  83. }
  84. pub fn send_message(siv: &mut Cursive, mut message: Message) -> Result<(), NetworkError> {
  85. message.sanitize();
  86. let appdata = get_appdata(siv);
  87. let url = fix_url(&appdata.persistent_data.api_endpoint).map_err(|x| NetworkError::UrlError(x))?;
  88. let password = appdata.persistent_data.api_password;
  89. let mut bytes = message.serialize_checked();
  90. utils::aes::encrypt_cbc(&mut bytes, &password);
  91. bytes.push(checksum(&bytes));
  92. let str = utils::binary::bin2hex(bytes);
  93. let resp = reqwest::blocking::Client::new()
  94. .post(format!("{}", url))
  95. .body(str)
  96. .timeout(Duration::from_secs(API_CONNECTION_TIMEOUT))
  97. .send()
  98. .map_err(|e| NetworkError::ReqwestError(e))?;
  99. if resp.status().is_success() {
  100. Ok(())
  101. } else {
  102. Err(NetworkError::StatusCodeError(resp.status().as_u16()))
  103. }
  104. }
  105. pub fn load_messages(siv: &mut Cursive) -> Result<(), NetworkError> {
  106. let appdata = get_appdata(siv);
  107. let url = fix_url(&appdata.persistent_data.api_endpoint).map_err(|x| NetworkError::UrlError(x))?;
  108. let password = appdata.persistent_data.api_password;
  109. let resp = reqwest::blocking::Client::new()
  110. .get(url)
  111. .timeout(Duration::from_secs(API_CONNECTION_TIMEOUT))
  112. .send()
  113. .map_err(|e| NetworkError::ReqwestError(e))?;
  114. if resp.status() != reqwest::StatusCode::OK {
  115. return Err(NetworkError::StatusCodeError(resp.status().as_u16()));
  116. }
  117. let bytes = resp.bytes().map_err(|e| NetworkError::ReqwestError(e))?;
  118. let contents = String::from_utf8_lossy(&bytes);
  119. let mut messages = vec![];
  120. for message_ser_hex in contents.split(",") {
  121. let Some(mut message_ser_bin) = utils::binary::hex2bin(message_ser_hex) else {
  122. continue;
  123. };
  124. if message_ser_bin.len() < 2 {
  125. continue;
  126. }
  127. // Checksum before decryption
  128. if checksum(&message_ser_bin[..message_ser_bin.len()-1]) != message_ser_bin[message_ser_bin.len()-1] {
  129. continue;
  130. }
  131. // Remove checksum
  132. message_ser_bin.pop();
  133. if utils::aes::decrypt_cbc(&mut message_ser_bin, &password).is_err() {
  134. continue;
  135. }
  136. let Ok(mut message) = Message::deserialize_checked(message_ser_bin) else {
  137. continue;
  138. };
  139. if message.is_valid() {
  140. message.sanitize();
  141. messages.push(message);
  142. }
  143. }
  144. siv.with_user_data(|appdata: &mut Appdata| {
  145. for message in messages {
  146. appdata.messages.insert(message.hash().to_string(), message);
  147. }
  148. });
  149. Ok(())
  150. }