lanis_rs/modules/
messages.rs

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    /// Parses a [Participant] from the name html <br>
43    /// <br>
44    /// NOTE: This will not include an id
45    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    /// Get all [ConversationOverview]'s (hidden and visible)
84    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                    /// If all conversations are hidden then this is missing for some reason
144                    #[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    /// Hides a visible conversation and returns the result if the hiding succeeded
238    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    /// Shows a hidden conversation and returns the result if the hiding succeeded
258    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    /// Get the full [Conversation]
278    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                    /// actually an [i32]
299                    error: String,
300                    message: String,
301                    time: i64,
302                    /// actually an [i32]
303                    #[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                    /// Actually an [i32]
324                    #[serde(rename = "Id")]
325                    id: String,
326                    #[serde(rename = "Uniquid")]
327                    uid: String,
328                    /// Actually an [i32]
329                    #[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                    /// None if a reply
361                    #[serde(rename = "groupOnly")]
362                    group_only: Option<String>,
363                    /// None if a reply
364                    #[serde(rename = "privateAnswerOnly")]
365                    private_answer_only: Option<String>,
366                    /// None if a reply
367                    #[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                    /// None if a reply
376                    #[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                    // Who is responseble for that shit
418                    &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    /// The subject of the [Conversation]
615    pub subject: String,
616    /// The person who created this [Conversation]
617    pub author: Participant,
618
619    /// Is the [Conversation] a group chat
620    pub group_chat: bool,
621    /// Does the [Conversation] only allow private answers
622    pub only_private_answers: bool,
623    /// Does the [Conversation] allow replies
624    pub can_reply: bool,
625
626    /// How many participants are in the [Conversation]
627    pub amount_participants: i32,
628    /// How many students / other are in the [Conversation]
629    pub amount_students: i32,
630    /// How many teachers are in the [Conversation]
631    /// Note: technically these are supervisors but teachers are as far as I know always supervisors
632    pub amount_teachers: i32,
633    /// How many parents are in the [Conversation]
634    pub amount_parents: i32,
635
636    /// All [Participant]'s / receiver that are in the [Conversation]
637    pub participants: Vec<Participant>,
638
639    /// All [Message]'s in the conversation
640    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    /// Reply to a [Conversation] (send a message) <br>
660    /// `message` supports lanis formatting (see [here](https://support.schulportal.hessen.de/knowledgebase.php?article=664) for more info) <br>
661    /// returns the UID of the new message (None if new message failed)
662    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                    /// UID
720                    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    /// The name of the [Participant]
743    pub name: String,
744    /// may be unknown if the [Participant] never wrote something in the chat
745    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    /// The date this [Message] was sent
757    pub date: DateTime<Utc>,
758    /// The author of this [Message]
759    pub author: Participant,
760    /// Was this [Message] send by the current [Account]
761    pub own: bool,
762    /// The content of this [Message]
763    pub content: String,
764}
765
766#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
767pub enum ConversationType {
768    /// No answers
769    NoAnswersAllowed,
770    /// Answers only to sender
771    PrivateAnswersOnly,
772    /// Answers to everyone
773    GroupOnly,
774    /// Private messages among themselves possible
775    OpenChat,
776}
777
778impl ConversationType {
779    /// Converts the enum to a String format that lanis expects
780    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
801/// Returns `true` if the use can freely choose what type a conversation should have
802pub 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
826/// Searches for a person using the provided query and returns the results as a [Vec] of [Receiver]'s <br> <br>
827///
828/// NOTE: Everything under 2 chars will return an empty [Vec<Receiver>]
829pub 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                    // TODO: Parent accounts
871                    "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
891/// ### Creates a new Conversation
892/// Receivers can be obtained with [search_receiver] <br>
893/// Receivers should be one or higher
894/// Text is the content of the message and supports lanis formatting (see [here](https://support.schulportal.hessen.de/knowledgebase.php?article=664)) <br>
895/// Text should not be empty <br> <br>
896///
897/// returns the new UID of the Conversation if creation was successful
898pub 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[]"), // This should be a crime
924            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                /// UID
943                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}