1use crate::base::account::AccountType;
2use crate::utils::constants::URL;
3use crate::utils::crypt::{decrypt_lanis_string_with_key, encrypt_lanis_data, LanisKeyPair};
4use chrono::{DateTime, Utc};
5use markup5ever::interface::TreeSink;
6use reqwest::header::HeaderValue;
7use reqwest::{Client, Response};
8use scraper::{Html, Selector};
9use serde::{Deserialize, Serialize};
10use std::fmt::Display;
11
12#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
13pub struct ConversationOverview {
14 pub id: i32,
15 pub uid: String,
16 pub sender: Participant,
17 pub receiver: Vec<Participant>,
18 pub subject: String,
19 pub date_time: DateTime<Utc>,
20 pub read: bool,
21 pub visible: bool,
22}
23
24impl ConversationOverview {
25 fn parse_name(html_string: &String) -> Result<String, Error> {
26 let mut html = Html::parse_fragment(&html_string);
27
28 let i_selector = Selector::parse("i.fas").unwrap();
29 let _ = html
30 .to_owned()
31 .select(&i_selector)
32 .map(|element| html.remove_from_parent(&element.id()));
33
34 Ok(html
35 .root_element()
36 .text()
37 .collect::<String>()
38 .trim()
39 .to_string())
40 }
41
42 fn parse_participant(html_string: &String) -> Result<Participant, Error> {
46 let mut html = Html::parse_fragment(&html_string);
47
48 let i_selector = Selector::parse("i.fas").unwrap();
49 let i_selector_teacher = Selector::parse("i.fas.fa-user").unwrap();
50 let i_selector_student = Selector::parse("i.fas.fa-child").unwrap();
51 let i_selector_parent = Selector::parse("i.fas.fa-user-circle").unwrap();
52
53 let account_type = {
54 if html.select(&i_selector_student).nth(0).is_some() {
55 AccountType::Student
56 } else if html.select(&i_selector_teacher).nth(0).is_some() {
57 AccountType::Teacher
58 } else if html.select(&i_selector_parent).nth(0).is_some() {
59 AccountType::Parent
60 } else {
61 AccountType::Unknown
62 }
63 };
64
65 let _ = html
66 .to_owned()
67 .select(&i_selector)
68 .map(|element| html.remove_from_parent(&element.id()));
69 let name = html
70 .root_element()
71 .text()
72 .collect::<String>()
73 .trim()
74 .to_string();
75
76 Ok(Participant {
77 id: None,
78 name,
79 account_type,
80 })
81 }
82
83 pub async fn get_root(
85 client: &Client,
86 keys: &LanisKeyPair,
87 ) -> Result<Vec<ConversationOverview>, Error> {
88 match client
89 .post(URL::MESSAGES)
90 .form(&[("a", "headers"), ("getType", "All"), ("last", "0")])
91 .header(
92 "X-Requested-With",
93 "XMLHttpRequest".parse::<HeaderValue>().unwrap(),
94 )
95 .send()
96 .await
97 {
98 Ok(response) => {
99 #[derive(Serialize, Deserialize)]
100 struct EncryptedResponseData {
101 total: i32,
102 rows: String,
103 }
104
105 let enc_text = response.text().await.map_err(|e| {
106 Error::Parsing(format!("failed to parse response as text '{}'", e))
107 })?;
108 let enc_data =
109 serde_json::from_str::<EncryptedResponseData>(&enc_text).map_err(|e| {
110 Error::Parsing(format!(
111 "failed to parse response JSON as EncryptedResponseData '{}'",
112 e
113 ))
114 })?;
115
116 let dec_rows_json_invalid =
117 decrypt_lanis_string_with_key(&enc_data.rows, &keys.public_key_string)
118 .await
119 .map_err(|e| Error::Crypto(format!("failed to decrypt rows '{}'", e)))?;
120 let dec_rows_json = format!(
121 "{}]",
122 dec_rows_json_invalid
123 .rsplit_once(']')
124 .unwrap_or(("[{}", "]"))
125 .0
126 );
127
128 #[derive(Serialize, Deserialize, Debug)]
129 #[serde(rename_all = "PascalCase")]
130 struct ConversationRowJson {
131 pub id: String,
132 pub uniquid: String,
133 pub sender: String,
134 pub sender_name: String,
135 #[serde(rename = "kuerzel")]
136 pub kuerzel: String,
137 pub betreff: String,
138 pub papierkorb: String,
139 #[serde(rename = "empf")]
140 pub empf: Vec<String>,
141 pub weitere_empfaenger: String,
142 pub datum_unix: i64,
143 #[serde(rename = "unread")]
145 pub unread: Option<i32>,
146 }
147
148 impl From<ConversationRowJson> for Result<ConversationOverview, Error> {
149 fn from(json_row: ConversationRowJson) -> Result<ConversationOverview, Error> {
150 let id = json_row.id.parse::<i32>().map_err(|e| {
151 Error::Parsing(format!("failed to parse id as i32 '{}'", e))
152 })?;
153 let uid = json_row.uniquid.to_owned();
154 let mut sender =
155 ConversationOverview::parse_participant(&json_row.sender_name)?;
156 let sender_id = json_row.sender.parse::<i32>().map_err(|e| {
157 Error::Parsing(format!("failed to parse sender as i32 '{}'", e))
158 })?;
159 sender.id = Some(sender_id);
160 let receiver = {
161 let mut result = Vec::new();
162 for receiver in &json_row.empf {
163 result.push(ConversationOverview::parse_participant(&receiver)?);
164 }
165 result
166 };
167 let subject = json_row.betreff.to_owned();
168 let date_time = DateTime::from_timestamp(json_row.datum_unix.to_owned(), 0)
169 .unwrap_or(DateTime::UNIX_EPOCH);
170 let read = match json_row.unread.unwrap_or(0) {
171 0 => true,
172 1 => false,
173 _ => {
174 return Err(Error::Parsing(String::from(
175 "failed to parse unread as bool (read) 'unexpected i32'",
176 )))
177 }
178 };
179 let visible = match json_row.papierkorb.as_str() {
180 "ja" => false,
181 "nein" => true,
182 _ => {
183 return Err(Error::Parsing(String::from(
184 "failed to parse visible as bool 'unexpected &str'",
185 )))
186 }
187 };
188
189 Ok(ConversationOverview {
190 id,
191 uid,
192 sender,
193 receiver,
194 subject,
195 date_time,
196 read,
197 visible,
198 })
199 }
200 }
201
202 let json_rows = serde_json::from_str::<Vec<ConversationRowJson>>(&dec_rows_json)
203 .map_err(|e| {
204 Error::Parsing(format!("failed to parse rows of decrypted json '{}'", e))
205 })?;
206 let overviews = {
207 let mut result: Vec<ConversationOverview> = Vec::new();
208 for json_row in json_rows {
209 result.push(<ConversationRowJson as Into<
210 Result<ConversationOverview, Error>,
211 >>::into(json_row)?);
212 }
213 result
214 };
215
216 Ok(overviews)
217 }
218 Err(error) => Err(Error::Network(format!(
219 "failed to get conversations '{}'",
220 error
221 ))),
222 }
223 }
224
225 async fn parse_recycle_response(&mut self, response: Response) -> Result<bool, Error> {
226 let response_bool = !response
227 .text()
228 .await
229 .map_err(|e| Error::Parsing(format!("failed to get text of response '{}'", e)))?
230 .parse::<bool>()
231 .map_err(|e| Error::Parsing(format!("failed to parse response as bool '{}'", e)))?;
232 let result = response_bool != self.visible;
233 self.visible = response_bool;
234 Ok(result)
235 }
236
237 pub async fn hide(&mut self, client: &Client) -> Result<bool, Error> {
239 match client
240 .post(URL::MESSAGES)
241 .form(&[("a", "deleteAll"), ("uniqid", self.uid.as_str())])
242 .header(
243 "X-Requested-With",
244 "XMLHttpRequest".parse::<HeaderValue>().unwrap(),
245 )
246 .send()
247 .await
248 {
249 Ok(response) => self.parse_recycle_response(response).await,
250 Err(e) => Err(Error::Network(format!(
251 "failed to hide conversation '{}'",
252 e
253 ))),
254 }
255 }
256
257 pub async fn show(&mut self, client: &Client) -> Result<bool, Error> {
259 match client
260 .post(URL::MESSAGES)
261 .form(&[("a", "recycleMsg"), ("uniqid", self.uid.as_str())])
262 .header(
263 "X-Requested-With",
264 "XMLHttpRequest".parse::<HeaderValue>().unwrap(),
265 )
266 .send()
267 .await
268 {
269 Ok(response) => Ok(!self.parse_recycle_response(response).await?),
270 Err(e) => Err(Error::Network(format!(
271 "failed to show conversation '{}'",
272 e
273 ))),
274 }
275 }
276
277 pub async fn get(&self, client: &Client, keys: &LanisKeyPair) -> Result<Conversation, Error> {
279 let enc_uid = encrypt_lanis_data(self.uid.as_bytes(), &keys.public_key_string);
280
281 let query = [("a", "read"), ("msg", self.uid.as_str())];
282 let enc_uid = enc_uid.await;
283 let form = [("a", "read"), ("uniqid", enc_uid.as_str())];
284 match client
285 .post(URL::MESSAGES)
286 .query(&query)
287 .form(&form)
288 .header(
289 "X-Requested-With",
290 "XMLHttpRequest".parse::<HeaderValue>().unwrap(),
291 )
292 .send()
293 .await
294 {
295 Ok(response) => {
296 #[derive(Serialize, Deserialize, Debug)]
297 struct EncJsonConversation {
298 error: String,
300 message: String,
301 time: i64,
302 #[serde(rename = "userId")]
304 user_id: String,
305 #[serde(rename = "ToolOptions")]
306 tool_options: JsonConversationToolOptions,
307 #[serde(rename = "UserTyp")]
308 user_typ: String,
309 }
310
311 #[derive(Serialize, Deserialize, Debug)]
312 struct JsonConversation {
313 error: i32,
314 message: JsonConversationMessage,
315 time: i64,
316 user_id: i32,
317 tool_options: JsonConversationToolOptions,
318 user_typ: String,
319 }
320
321 #[derive(Serialize, Deserialize, Debug)]
322 struct JsonConversationMessage {
323 #[serde(rename = "Id")]
325 id: String,
326 #[serde(rename = "Uniquid")]
327 uid: String,
328 #[serde(rename = "Sender")]
330 sender_id: String,
331 sender_type: String,
332 }
333
334 #[derive(Serialize, Deserialize, Debug)]
335 struct JsonConversationMessageStats {
336 #[serde(rename = "teilnehmer")]
337 pub participants: i32,
338 #[serde(rename = "betreuer")]
339 pub supervisors: i32,
340 #[serde(rename = "eltern")]
341 pub parents: i32,
342 }
343
344 #[derive(Serialize, Deserialize, Debug)]
345 struct JsonConversationToolOptions {
346 #[serde(rename = "AllowSuSToSuSMessages")]
347 allow_sus_to_sus_messages: String,
348 }
349
350 #[derive(Serialize, Deserialize, Debug)]
351 pub struct DecJsonMessageField {
352 #[serde(rename = "Id")]
353 id: String,
354 #[serde(rename = "Uniquid")]
355 uid: String,
356 #[serde(rename = "Sender")]
357 sender: String,
358 #[serde(rename = "SenderArt")]
359 sender_type: String,
360 #[serde(rename = "groupOnly")]
362 group_only: Option<String>,
363 #[serde(rename = "privateAnswerOnly")]
365 private_answer_only: Option<String>,
366 #[serde(rename = "noAnswerAllowed")]
368 no_answer_allowed: Option<String>,
369 #[serde(rename = "Betreff")]
370 subject: String,
371 #[serde(rename = "Datum")]
372 date: String,
373 #[serde(rename = "Inhalt")]
374 content: String,
375 #[serde(rename = "Papierkorb")]
377 hidden: Option<String>,
378 #[serde(rename = "statistik")]
379 stats: JsonConversationMessageStats,
380 own: bool,
381 #[serde(rename = "username")]
382 sender_name: String,
383 noanswer: bool,
384 #[serde(rename = "Delete")]
385 delete: String,
386 #[serde(rename = "reply")]
387 replies: Vec<DecJsonMessageField>,
388 private: i32,
389 #[serde(rename = "ungelesen")]
390 unread: bool,
391 #[serde(rename = "AntwortAufAusgeblendeteNachricht")]
392 answer_to_hidden: bool,
393 }
394
395 let text = response.text().await.map_err(|e| {
396 Error::Parsing(format!("failed to parse text of response '{}'", e))
397 })?;
398 let encrypted_json =
399 serde_json::from_str::<EncJsonConversation>(&text).map_err(|e| {
400 Error::Parsing(format!("failed to parse encrypted json '{}'", e))
401 })?;
402 let decrypted_json = {
403 let mut result = encrypted_json;
404 let decrypted_message =
405 decrypt_lanis_string_with_key(&result.message, &keys.public_key_string)
406 .await
407 .map_err(|e| {
408 Error::Crypto(format!("failed to decrypt message json '{}'", e))
409 })?;
410 result.message = format!(
411 "{}}}",
412 decrypted_message.rsplit_once("}").unwrap_or_default().0
413 );
414 result
415 };
416 let decrypted_json_message_field = serde_json::from_str::<DecJsonMessageField>(
417 &decrypted_json.message.replace(
419 "\"AntwortAufAusgeblendeteNachricht\":\"on\"",
420 "\"AntwortAufAusgeblendeteNachricht\":true",
421 ),
422 )
423 .map_err(|e| {
424 Error::Parsing(format!(
425 "failed to parse message field in decrypted json '{}'",
426 e
427 ))
428 })?;
429
430 fn parse_messages(json: &DecJsonMessageField) -> Result<Vec<Message>, Error> {
431 let mut messages = Vec::new();
432 messages.push({
433 let id = json.id.parse().map_err(|e| {
434 Error::Parsing(format!("failed to parse message id '{}'", e))
435 })?;
436 let mut date_split = json.date.split_once(" ").unwrap_or_default();
437 let mut date = date_split.0.to_string();
438 if date_split.0 == "heute" {
439 let new_date =
440 format!("{}", chrono::Local::now().date_naive().format("%d.%m.%Y"));
441 date = new_date
442 }
443 if date_split.0 == "gestern" {
444 let new_date = format!(
445 "{}",
446 (chrono::Local::now() - chrono::Duration::days(1))
447 .date_naive()
448 .format("%d.%m.%Y")
449 );
450 date = new_date
451 }
452 date_split.0 = date.as_str();
453 let date = date_time_string_to_datetime(
454 date_split.0,
455 &format!("{}:00", date_split.1),
456 )
457 .map_err(|e| {
458 Error::DateTime(format!(
459 "failed to parse date & time of message '{:?}'",
460 e
461 ))
462 })?
463 .to_utc();
464 let author = {
465 let id = Some(json.sender.parse().map_err(|e| {
466 Error::Parsing(format!("failed to parse sender id '{}'", e))
467 })?);
468 let name = ConversationOverview::parse_name(&json.sender_name)?;
469 let account_type = match json.sender_type.as_str() {
470 "Teilnehmer" => AccountType::Student,
471 "Betreuer" => AccountType::Teacher,
472 "Eltern" => AccountType::Parent,
473 _ => AccountType::Unknown,
474 };
475
476 Participant {
477 id,
478 name,
479 account_type,
480 }
481 };
482
483 let own = json.own.to_owned();
484 let content = json.content.to_owned();
485 let html_content =
486 Html::parse_document(&format!("<body>{}</body>", content));
487 let content = html_content
488 .root_element()
489 .text()
490 .collect::<String>()
491 .trim()
492 .to_owned();
493
494 Message {
495 id,
496 date,
497 author,
498 own,
499 content,
500 }
501 });
502
503 for reply in &json.replies {
504 let reply_messages = parse_messages(reply)?;
505 messages.extend(reply_messages);
506 }
507
508 Ok(messages)
509 }
510
511 let messages = parse_messages(&decrypted_json_message_field)?;
512
513 async fn parse_participants(
514 messages: &Vec<Message>,
515 receivers: &Vec<Participant>,
516 ) -> Result<Vec<Participant>, Error> {
517 let mut participants = Vec::new();
518 participants.append(receivers.to_owned().as_mut());
519
520 for message in messages {
521 if !participants.contains(&message.author) {
522 participants.push(message.author.to_owned());
523 }
524 }
525
526 Ok(participants)
527 }
528
529 let participants = parse_participants(&messages, &self.receiver).await?;
530
531 let id = self.id.to_owned();
532 let uid = self.uid.to_owned();
533
534 async fn match_german_string_bool(string: &Option<String>) -> Result<bool, Error> {
535 if let Some(string) = string {
536 Ok(match string.as_str() {
537 "ja" => true,
538 "nein" => false,
539 _ => false,
540 })
541 } else {
542 Err(Error::Parsing(String::from(
543 "group chat entry is missing 'is None'",
544 )))
545 }
546 }
547
548 let group_chat =
549 match_german_string_bool(&decrypted_json_message_field.group_only).await?;
550 let only_private_answers =
551 match_german_string_bool(&decrypted_json_message_field.private_answer_only)
552 .await?;
553 let can_reply =
554 !match_german_string_bool(&decrypted_json_message_field.no_answer_allowed)
555 .await?;
556
557 let mut amount_students = decrypted_json_message_field.stats.participants;
558 let mut amount_teachers = decrypted_json_message_field.stats.supervisors;
559 let mut amount_parents = decrypted_json_message_field.stats.parents;
560
561 match self.sender.account_type {
562 AccountType::Student => amount_students += 1,
563 AccountType::Teacher => amount_teachers += 1,
564 AccountType::Parent => amount_parents += 1,
565 AccountType::Unknown => (),
566 }
567
568 let amount_participants = amount_students + amount_teachers + amount_parents;
569
570 let visible = self.visible;
571 let read = self.read;
572 let date_time = self.date_time.to_owned();
573
574 let subject = self.subject.to_owned();
575 let author = self.sender.to_owned();
576
577 Ok(Conversation {
578 id,
579 uid,
580 visible,
581 read,
582 date_time,
583
584 subject,
585 author,
586
587 group_chat,
588 only_private_answers,
589 can_reply,
590
591 amount_participants,
592 amount_students,
593 amount_teachers,
594 amount_parents,
595
596 participants,
597
598 messages,
599 })
600 }
601 Err(e) => Err(Error::Network(format!("failed to post message '{e}'"))),
602 }
603 }
604}
605
606#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
607pub struct Conversation {
608 pub id: i32,
609 pub uid: String,
610 pub visible: bool,
611 pub read: bool,
612 pub date_time: DateTime<Utc>,
613
614 pub subject: String,
616 pub author: Participant,
618
619 pub group_chat: bool,
621 pub only_private_answers: bool,
623 pub can_reply: bool,
625
626 pub amount_participants: i32,
628 pub amount_students: i32,
630 pub amount_teachers: i32,
633 pub amount_parents: i32,
635
636 pub participants: Vec<Participant>,
638
639 pub messages: Vec<Message>,
641}
642
643impl Conversation {
644 pub async fn refresh(&mut self, client: &Client, key_pair: &LanisKeyPair) -> Result<(), Error> {
645 let overview = ConversationOverview {
646 id: self.id,
647 uid: self.uid.to_owned(),
648 sender: self.author.to_owned(),
649 receiver: self.participants.to_owned(),
650 subject: self.subject.to_owned(),
651 date_time: self.date_time.to_owned(),
652 read: self.read,
653 visible: self.visible,
654 };
655
656 Ok(*self = ConversationOverview::get(&overview, client, key_pair).await?)
657 }
658
659 pub async fn reply(
663 &self,
664 message: &str,
665 client: &Client,
666 key_pair: &LanisKeyPair,
667 ) -> Result<Option<String>, Error> {
668 #[derive(Serialize, Deserialize)]
669 struct JSON {
670 to: String,
671 #[serde(rename = "groupOnly")]
672 group_only: String,
673 #[serde(rename = "privateAnswerOnly")]
674 private_answers_only: String,
675 message: String,
676 #[serde(rename = "replyToMsg")]
677 replay_to_message: String,
678 }
679
680 let sender_id = match self.messages.get(0) {
681 Some(msg) => match msg.author.id {
682 Some(id) => id,
683 None => return Err(Error::Parsing(String::from("no author"))),
684 },
685 None => return Err(Error::Parsing(String::from("no messages"))),
686 };
687
688 fn bool_to_german(bool: &bool) -> String {
689 if *bool {
690 "ja".to_string()
691 } else {
692 "nein".to_string()
693 }
694 }
695
696 let json = JSON {
697 to: sender_id.to_string(),
698 group_only: bool_to_german(&self.group_chat),
699 private_answers_only: bool_to_german(&self.only_private_answers),
700 message: message.trim().to_string(),
701 replay_to_message: self.uid.to_owned(),
702 };
703
704 let json_string = serde_json::to_string(&json)
705 .map_err(|e| Error::Parsing(format!("failed to serialize message '{e}'")))?;
706 let enc_json_string =
707 encrypt_lanis_data(json_string.as_bytes(), &key_pair.public_key_string).await;
708
709 match client
710 .post(URL::MESSAGES)
711 .form(&[("a", "reply"), ("c", enc_json_string.as_str())])
712 .send()
713 .await
714 {
715 Ok(response) => {
716 #[derive(Serialize, Deserialize)]
717 struct ResponseJson {
718 back: bool,
719 id: String,
721 }
722
723 let text = response.text().await.map_err(|e| {
724 Error::Parsing(format!("failed to parse response as text: {}", e))
725 })?;
726 let json: ResponseJson = serde_json::from_str(&text)
727 .map_err(|e| Error::Parsing(format!("failed to deserialize JSON: {}", e)))?;
728
729 if !json.back {
730 return Ok(None);
731 }
732 Ok(Some(json.id))
733 }
734 Err(e) => Err(Error::Network(format!("failed to send message '{e}'"))),
735 }
736 }
737}
738
739#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
740pub struct Participant {
741 pub id: Option<i32>,
742 pub name: String,
744 pub account_type: AccountType,
746}
747
748#[allow(unused_imports)]
749use crate::base::account::Account;
750use crate::utils::datetime::date_time_string_to_datetime;
751use crate::Error;
752
753#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
754pub struct Message {
755 pub id: i32,
756 pub date: DateTime<Utc>,
758 pub author: Participant,
760 pub own: bool,
762 pub content: String,
764}
765
766#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
767pub enum ConversationType {
768 NoAnswersAllowed,
770 PrivateAnswersOnly,
772 GroupOnly,
774 OpenChat,
776}
777
778impl ConversationType {
779 pub fn to_lanis_string(&self) -> String {
781 match &self {
782 ConversationType::NoAnswersAllowed => String::from("noAnswerAllowed"),
783 ConversationType::PrivateAnswersOnly => String::from("privateAnswerOnly"),
784 ConversationType::GroupOnly => String::from("groupOnly"),
785 ConversationType::OpenChat => String::from("openChat"),
786 }
787 }
788}
789
790impl Display for ConversationType {
791 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792 match &self {
793 ConversationType::NoAnswersAllowed => write!(f, "NoAnswersAllowed"),
794 ConversationType::PrivateAnswersOnly => write!(f, "PrivateAnswersOnly"),
795 ConversationType::GroupOnly => write!(f, "GroupOnly"),
796 ConversationType::OpenChat => write!(f, "OpenChat"),
797 }
798 }
799}
800
801pub async fn can_choose_type(client: &Client) -> Result<bool, Error> {
803 match client.get(URL::MESSAGES).send().await {
804 Ok(response) => {
805 let html = Html::parse_document(
806 &response
807 .text()
808 .await
809 .map_err(|e| Error::Parsing(format!("failed to parse message '{:?}'", e)))?,
810 );
811 let options_selector = Selector::parse("#MsgOptions").unwrap();
812
813 Ok(html.select(&options_selector).nth(0).is_some())
814 }
815 Err(e) => Err(Error::Network(format!("failed to get message page '{e}'"))),
816 }
817}
818
819#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
820pub struct Receiver {
821 pub id: String,
822 pub name: String,
823 pub account_type: AccountType,
824}
825
826pub async fn search_receiver(query: &str, client: &Client) -> Result<Vec<Receiver>, Error> {
830 if query.len() < 2 {
831 return Ok(Vec::new());
832 }
833
834 match client
835 .get(URL::MESSAGES)
836 .query(&[("a", "searchRecipt"), ("q", query)])
837 .send()
838 .await
839 {
840 Ok(response) => {
841 let text = response.text().await.map_err(|e| {
842 Error::Parsing(format!("failed to parse response as text '{:?}'", e))
843 })?;
844 if text.contains("\"items\":null") {
845 return Ok(Vec::new());
846 }
847
848 #[derive(Serialize, Deserialize)]
849 struct JSON {
850 items: Vec<JSONItem>,
851 }
852
853 #[derive(Serialize, Deserialize)]
854 struct JSONItem {
855 #[serde(rename = "type")]
856 account_type: String,
857 id: String,
858 text: String,
859 }
860
861 let json: JSON = serde_json::from_str(&text)
862 .map_err(|e| Error::Parsing(format!("failed to parse response as JSON: {}", e)))?;
863
864 let mut result = Vec::new();
865
866 for item in json.items {
867 let id = item.id;
868 let name = item.text;
869 let account_type = match item.account_type.as_str() {
870 "sus" => AccountType::Student,
872 "lul" => AccountType::Teacher,
873 _ => AccountType::Unknown,
874 };
875
876 result.push(Receiver {
877 id,
878 name,
879 account_type,
880 })
881 }
882
883 Ok(result)
884 }
885 Err(e) => Err(Error::Network(format!(
886 "failed to perform a search query '{e}'"
887 ))),
888 }
889}
890
891pub async fn create_conversation(
899 receiver: &Vec<Receiver>,
900 subject: &str,
901 text: &str,
902 client: &Client,
903 key_pair: &LanisKeyPair,
904) -> Result<Option<String>, Error> {
905 #[derive(Serialize, Deserialize)]
906 struct JSONItem {
907 name: String,
908 value: String,
909 }
910
911 let mut json_vec = Vec::new();
912 json_vec.push(JSONItem {
913 name: String::from("subject"),
914 value: String::from(subject),
915 });
916 json_vec.push(JSONItem {
917 name: String::from("text"),
918 value: String::from(text),
919 });
920
921 for receiver in receiver {
922 json_vec.push(JSONItem {
923 name: String::from("to[]"), value: receiver.id.to_owned(),
925 })
926 }
927
928 let json = serde_json::to_string(&json_vec)
929 .map_err(|e| Error::Parsing(format!("failed to serialize JSON: {}", e)))?;
930 let enc_json = encrypt_lanis_data(json.as_bytes(), &key_pair.public_key_string).await;
931
932 match client
933 .post(URL::MESSAGES)
934 .form(&[("a", "newmessage"), ("c", enc_json.as_str())])
935 .send()
936 .await
937 {
938 Ok(response) => {
939 #[derive(Serialize, Deserialize)]
940 struct ResponseJson {
941 back: bool,
942 id: String,
944 }
945
946 let text = response
947 .text()
948 .await
949 .map_err(|e| Error::Parsing(format!("failed to parse response as text: {}", e)))?;
950 let json: ResponseJson = serde_json::from_str(&text)
951 .map_err(|e| Error::Parsing(format!("failed to deserialize JSON: {}", e)))?;
952
953 if !json.back {
954 return Ok(None);
955 }
956 Ok(Some(json.id))
957 }
958 Err(e) => Err(Error::Network(format!(
959 "failed to create conversation '{e}'"
960 ))),
961 }
962}