1use serde::{Deserialize, Serialize};
2
3pub mod base;
4pub mod modules;
5pub mod utils;
6
7#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
8pub enum Feature {
9 LanisTimetable,
10 MeinUnttericht,
11 FileStorage,
12 MessagesBeta,
13 Calendar,
14}
15
16#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
17pub enum Error {
18 Network(String),
19 Parsing(String),
21 Crypto(String),
22 Html(String),
23 Credentials(String),
25 UntisAPI(String),
27 DateTime(String),
29 Threading(String),
31 SchoolNotFound(String),
33 KeyPair,
35 LoginTimeout(u32),
37 LessonUploadError(LessonUploadError),
39 ServerSide(String),
41 FileSystem(String),
43 InvalidInput(String),
45}
46
47impl std::fmt::Display for Error {
48 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
49 match self {
50 Error::Network(e) => write!(f, "Error::Network({e})"),
51 Error::Parsing(e) => write!(f, "Error::Parsing({e})"),
52 Error::Crypto(e) => write!(f, "Error::Crypto({e})"),
53 Error::Html(e) => write!(f, "Error::Html({e})"),
54 Error::Credentials(e) => write!(f, "Error::Credentials({e})"),
55 Error::UntisAPI(e) => write!(f, "Error::UntisAPI({e})"),
56 Error::DateTime(e) => write!(f, "Error::DateTime({e})"),
57 Error::Threading(e) => write!(f, "Error::Threading({e})"),
58 Error::SchoolNotFound(e) => write!(f, "Error::SchoolNotFound({e})"),
59 Error::KeyPair => write!(f, "Error::KeyPair"),
60 Error::LoginTimeout(e) => write!(f, "Error::LoginTimeout({e})"),
61 Error::LessonUploadError(e) => write!(f, "Error::LessonUploadError({e})"),
62 Error::ServerSide(e) => write!(f, "Error::ServerSide({e})"),
63 Error::FileSystem(e) => write!(f, "Error::FileSystem({e})"),
64 Error::InvalidInput(e) => write!(f, "Error::InvalidInput({e})"),
65 }
66 }
67}
68
69#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
70pub enum LessonUploadError {
71 NoInfo,
73 NoDetailedInfo,
76 Network(String),
77 WrongPassword,
78 EncryptionFailed(String),
79 DeletionFailed,
81 Unknown,
82 UnknownServerError,
83}
84
85impl std::fmt::Display for LessonUploadError {
86 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
87 match self {
88 LessonUploadError::NoInfo => write!(f, "LessonUploadError::NoInfo"),
89 LessonUploadError::NoDetailedInfo => write!(f, "LessonUploadError::NoDetailedInfo"),
90 LessonUploadError::Network(e) => write!(f, "LessonUploadError::Network({e})"),
91 LessonUploadError::WrongPassword => write!(f, "LessonUploadError::WrongPassword"),
92 LessonUploadError::EncryptionFailed(e) => {
93 write!(f, "LessonUploadError::EncryptionFailed({})", e)
94 }
95 LessonUploadError::DeletionFailed => write!(f, "LessonUploadError::DeletionFailed"),
96 LessonUploadError::Unknown => write!(f, "LessonUploadError::Unknown"),
97 LessonUploadError::UnknownServerError => {
98 write!(f, "LessonUploadError::UnknownServerError")
99 }
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 use crate::base::account::{Account, AccountSecrets, AccountType, UntisSecrets};
109 use crate::base::schools::{get_school_id, get_schools, School};
110 use crate::modules::lessons::get_lessons;
111 use crate::modules::timetable;
112 use crate::modules::timetable::{Provider, Week};
113
114 use crate::modules::file_storage::FileStoragePage;
115 use crate::modules::messages::{
116 can_choose_type, create_conversation, search_receiver, ConversationOverview,
117 };
118 use crate::utils::crypt::{decrypt_any, encrypt_any};
119 use base::account;
120 use modules::calendar::{
121 self, CalendarExportFileType, CalendarExportFileTypePDF, CalendarExports,
122 };
123 use std::path::Path;
124 use std::{env, fs};
125 use stopwatch_rs::StopWatch;
126
127 #[tokio::test]
128 async fn test_encryption() {
129 let text = fs::read_to_string("test_file.txt").unwrap();
130
131 #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
132 struct TestText {
133 text: String,
134 }
135
136 let data = TestText { text };
137 let key = b"ILikeToast12!EncryptionIsSoNice!";
138
139 let encrypted = encrypt_any(&data, key).await.unwrap();
140 let decrypted: TestText = decrypt_any(&encrypted, key).await.unwrap();
141
142 assert_eq!(data, decrypted);
143 }
144
145 #[tokio::test]
146 async fn test_schools_get_school_id() {
147 let mut schools: Vec<School> = vec![];
148 schools.push(School {
149 id: 3120,
150 name: String::from("The Almighty Rust School"),
151 city: String::from("Rust City"),
152 });
153 schools.push(School {
154 id: 3920,
155 name: String::from("The Almighty Rust School"),
156 city: String::from("Rust City 2"),
157 });
158 schools.push(School {
159 id: 4031,
160 name: String::from("The Almighty Rust School 2"),
161 city: String::from("Rust City"),
162 });
163 let result = get_school_id("The Almighty Rust School", "Rust City 2", &schools).await;
164 assert_eq!(result, 3920);
165 }
166
167 #[tokio::test]
168 async fn test_schools_get_schools() {
169 let client = reqwest::Client::new();
170
171 let result = get_schools(&client).await.unwrap();
172 assert_eq!(result.get(0).unwrap().id, 3354);
173 }
174
175 async fn create_account() -> Account {
176 let mut stopwatch = StopWatch::start();
177
178 let account_secrets = AccountSecrets::new(
179 {
180 env::var("LANIS_SCHOOL_ID")
181 .unwrap_or_else(|e| {
182 println!("Error ({})\nDid you define 'LANIS_SCHOOL_ID' in env?", e);
183 String::from("0")
184 })
185 .parse()
186 .expect(
187 "Couldn't parse 'LANIS_SCHOOL_ID'.\nDid you define SCHOOL_ID as an i32?",
188 )
189 },
190 {
191 env::var("LANIS_USERNAME").unwrap_or_else(|e| {
192 println!("Error ({})\nDid you define 'LANIS_USERNAME' in env?", e);
193 String::from("")
194 })
195 },
196 {
197 env::var("LANIS_PASSWORD").unwrap_or_else(|e| {
198 println!("Error ({})\nDid you define 'LANIS_PASSWORD' in env?", e);
199 String::from("")
200 })
201 },
202 );
203 let account = Account::new(account_secrets).await.unwrap();
204 println!(
205 "account::new() took {}ms",
206 stopwatch.split().split.as_millis()
207 );
208
209 account
210 }
211
212 #[tokio::test]
213 async fn test_account() {
214 let account = create_account().await;
215
216 let mut stopwatch = StopWatch::start();
217 account.prevent_logout().await.unwrap();
218 println!(
219 "account.prevent_logout() took {}ms",
220 stopwatch.split().split.as_millis()
221 );
222 println!();
223
224 println!("Private Key:\n{}", account.key_pair.private_key_string);
225 println!("Public Key:\n{}", account.key_pair.public_key_string);
226
227 println!("Account Info: {:?}", account.info);
228 println!("Account Type: {:?}", account.account_type);
229
230 println!()
231 }
232
233 #[tokio::test]
234 async fn test_timetable() {
235 let mut account = create_account().await;
236
237 if account.is_supported(Feature::LanisTimetable) {
238 let mut stopwatch = StopWatch::start();
240 let time_table_week = Week::new(
241 Provider::Lanis(timetable::LanisType::All),
242 &account.client,
243 chrono::Local::now().date_naive(),
244 )
245 .await
246 .unwrap();
247 let ms = stopwatch.split().split.as_millis();
248 println!("Lanis All: {:?}", time_table_week);
249 println!("Week::new() took {}ms", ms);
250 println!();
251
252 let mut stopwatch = StopWatch::start();
254 let time_table_week = Week::new(
255 Provider::Lanis(timetable::LanisType::Own),
256 &account.client,
257 chrono::Local::now().date_naive(),
258 )
259 .await
260 .unwrap();
261 let ms = stopwatch.split().split.as_millis();
262 println!("Lanis Own: {:?}", time_table_week);
263 println!("Week::new() took {}ms", ms);
264 println!();
265 } else {
266 println!("LanisTimetable is not supported by this account! Skipping.");
267 }
268
269 if env::var("UNTIS_TEST_TIMETABLE")
271 .unwrap_or("FALSE".to_string())
272 .eq("TRUE")
273 {
274 let mut stopwatch = StopWatch::start();
275 let school_name = env::var("UNTIS_SCHOOL_NAME")
276 .expect("Couldn't find 'UNTIS_SCHOOL_NAME' in env! Did you set it?");
277 let username = env::var("UNTIS_USERNAME")
278 .expect("Couldn't find 'UNTIS_USERNAME' in env! Did you set it?");
279 let password = env::var("UNTIS_PASSWORD")
280 .expect("Couldn't find 'UNTIS_PASSWORD' in env! Did you set it?");
281
282 let secrets = UntisSecrets::new(school_name, username, password);
283 account.secrets.untis_secrets = Some(secrets);
284
285 let time_table_week = Week::new(
286 Provider::Untis(account.secrets.untis_secrets.as_ref().unwrap().clone()),
287 &account.client,
288 chrono::Local::now().date_naive() - chrono::Duration::weeks(1),
289 )
290 .await
291 .unwrap();
292 let ms = stopwatch.split().split.as_millis();
293 println!("Untis: {:?}", time_table_week);
294 println!("Week::new() took {}ms", ms);
295 }
296
297 println!();
298 }
299
300 #[tokio::test]
301 async fn test_lessons() {
302 let account = create_account().await;
303 if account.account_type != AccountType::Student {
304 println!("Not a student account! Skipping!");
305 return;
306 }
307
308 if account.is_supported(Feature::MeinUnttericht) {
309 let mut stopwatch = StopWatch::start();
310 let mut lessons = get_lessons(&account).await.unwrap();
311 println!(
312 "get_lessons() took {}ms",
313 stopwatch.split().split.as_millis()
314 );
315
316 let mut stopwatch = StopWatch::start();
317 for lesson in lessons.iter_mut() {
318 println!("\tid: {}", lesson.id);
319 println!("\turl: {}", lesson.url);
320 println!("\tname: {}", lesson.name);
321 println!("\tteacher: {}", lesson.teacher);
322 println!("\tteacher_short: {:?}", lesson.teacher_short);
323 println!("\tattendances: {:?}", lesson.attendances);
324 println!("\tentry_latest: {:?}", lesson.entry_latest);
325 let mut stopwatch = StopWatch::start();
326 lesson.set_data(&account).await.unwrap();
327 println!(
328 "\tlesson.set_data() took {}ms",
329 stopwatch.split().split.as_millis()
330 );
331 println!("\tmarks: {:?}", lesson.marks);
332 println!("\tentries:");
333 let mut stopwatch = StopWatch::start();
334 for mut entry in lesson.entries.clone().unwrap() {
335 println!("\t\t{:?}", entry);
336 if entry.homework.is_some() {
337 let mut homework = entry.homework.clone().unwrap();
338 let mut new_homework = !homework.completed;
339
340 let mut stopwatch = StopWatch::start();
341 homework
342 .set_homework(new_homework, lesson.id, entry.id, &account.client)
343 .await
344 .unwrap();
345 println!(
346 "\t\t\tHomework was changed from {} to {} and took {}ms",
347 !homework.completed,
348 new_homework,
349 stopwatch.split().split.as_millis()
350 );
351 entry.homework = Some(homework.to_owned());
352 println!("\t\t\tHomework after change: {:?}", entry.homework);
353
354 new_homework = !new_homework;
355
356 let mut stopwatch = StopWatch::start();
357 homework
358 .set_homework(new_homework, lesson.id, entry.id, &account.client)
359 .await
360 .unwrap();
361 println!(
362 "\t\t\tHomework was changed from {} to {} and took {}",
363 !homework.completed,
364 new_homework,
365 stopwatch.split().split.as_millis()
366 );
367 entry.homework = Some(homework);
368 println!("\t\t\tHomework after change: {:?}", entry.homework);
369 }
370 if entry.uploads.is_some() {
371 let mut uploads = entry.uploads.clone().unwrap();
372 for upload in &mut uploads {
373 let mut stopwatch = StopWatch::start();
374 upload.info = Some(upload.get_info(&account.client).await.unwrap());
375 println!(
376 "\t\t\tupload.get_info() took {}ms",
377 stopwatch.split().split.as_millis()
378 );
379 println!("\t\t\tUpload: {:?}", upload);
380 if upload.state {
381 let mut stopwatch = StopWatch::start();
382 let path = env::var("LANIS_TEST_FILE").unwrap_or_else(|e| {
383 panic!(
384 "Error ({})\nDid you define 'LANIS_TEST_FILE' in env?",
385 e
386 )
387 });
388 let path = Path::new(&path);
389 let status =
390 upload.upload(vec![path], &account.client).await.unwrap();
391 let ms = stopwatch.split().split.as_millis();
392 println!("\t\t\tUploaded test file: {}", upload.url);
393 println!("\t\t\t\tUrl: {}", upload.url);
394 println!("\t\t\t\tStatus: {:?}", status);
395 println!("\t\t\tupload.upload() took {}ms", ms);
396
397 let i = {
398 upload.info =
399 Some(upload.get_info(&account.client).await.unwrap());
400 let own_files = upload.info.clone().unwrap().own_files;
401 let mut i = -1;
402 for file in own_files {
403 if file.name == status.get(0).unwrap().name {
404 i = file.index;
405 }
406 }
407
408 i
409 };
410
411 let mut stopwatch = StopWatch::start();
413 if i != -1 {
414 upload.delete(&i, &account).await.unwrap();
415 }
416 println!(
417 "\t\t\tupload.delete() took {}ms",
418 stopwatch.split().split.as_millis()
419 );
420 }
421 }
422 }
423 }
424 println!(
425 "\tIteration of all entries took {}ms",
426 stopwatch.split().split.as_millis()
427 );
428 println!("\texams:");
429 for exam in lesson.exams.clone().unwrap() {
430 println!("\t\t{:?}", exam)
431 }
432
433 println!(" ");
434 }
435 println!(
436 "Iteration of all lessons took {}ms",
437 stopwatch.split().split.as_millis()
438 );
439
440 println!()
441 } else {
442 println!("Lessons are not supported by this account! Skipping.");
443 }
444 }
445
446 #[tokio::test]
447 async fn test_file_storage() {
448 let account = create_account().await;
449
450 if !account.is_supported(Feature::FileStorage) {
451 println!("File Storage is not supported by this account! Skipping.");
452 return;
453 }
454
455 print!("Getting root page... ");
456 let mut stopwatch = StopWatch::start();
457 let root_page = FileStoragePage::get_root(&account.client).await.unwrap();
458 let ms = stopwatch.split().split.as_millis();
459 println!("Took {} ms", ms);
460 println!("Root page:\n{:#?}", root_page);
461 println!();
462
463 if let Some(node) = root_page.folder_nodes.get(0) {
464 print!("Getting folder node page... ");
465 let mut stopwatch = StopWatch::start();
466 let first_page = FileStoragePage::get(node.id, &account.client)
467 .await
468 .unwrap();
469 let ms = stopwatch.split().split.as_millis();
470 println!("Took {} ms", ms);
471 println!("First page:\n{:#?}", first_page);
472 println!();
473
474 if let Some(node) = first_page.file_nodes.get(0) {
475 let path = format!("/tmp/{}", node.name);
476 print!("Downloading first file node to '{}'... ", path);
477 let mut stopwatch = StopWatch::start();
478
479 node.download(&path, &account.client).await.unwrap();
480
481 let ms = stopwatch.split().split.as_millis();
482 println!("Took {}ms", ms);
483
484 print!("Deleting '{}'... ", path);
485 let mut stopwatch = StopWatch::start();
486
487 tokio::fs::remove_file(path).await.unwrap();
488
489 let ms = stopwatch.split().split.as_millis();
490 println!("Took {}ms", ms);
491 }
492 }
493
494 println!();
495 }
496
497 #[tokio::test]
498 async fn test_messages() {
499 let account = create_account().await;
500
501 print!("Getting root page of conversations... ");
502 let mut stopwatch = StopWatch::start();
503 let overviews = ConversationOverview::get_root(&account.client, &account.key_pair)
504 .await
505 .unwrap();
506 let ms = stopwatch.split().split.as_millis();
507 println!("Took {}ms", ms);
508 println!("Conversation overviews: {:#?}", overviews);
509
510 for mut overview in overviews.to_owned() {
511 println!("Current overview: {}", overview.subject);
512 if overview.visible {
513 println!("\tBefore: {}", overview.visible);
514 print!("\tHiding conversation overview... ");
515 let mut stopwatch = StopWatch::start();
516 let result = overview.hide(&account.client).await.unwrap();
517 let ms = stopwatch.split().split.as_millis();
518 println!("Took {}ms", ms);
519 println!("\tResult: {}", result);
520
521 println!("\tNow: {}", overview.visible);
522
523 print!("\tShowing conversation overview... ");
524 let mut stopwatch = StopWatch::start();
525 let result = overview.show(&account.client).await.unwrap();
526 let ms = stopwatch.split().split.as_millis();
527 println!("Took {}ms", ms);
528 println!("\tResult: {}", result);
529 println!("\tAfter: {}", overview.visible);
530 } else {
531 println!("\tBefore: {}", overview.visible);
532 print!("\tShowing conversation overview... ");
533 let mut stopwatch = StopWatch::start();
534 let result = overview.show(&account.client).await.unwrap();
535 let ms = stopwatch.split().split.as_millis();
536 println!("Took {}ms", ms);
537 println!("\tResult: {}", result);
538
539 println!("\tNow: {}", overview.visible);
540
541 print!("\tHiding conversation overview... ");
542 let mut stopwatch = StopWatch::start();
543 let result = overview.hide(&account.client).await.unwrap();
544 let ms = stopwatch.split().split.as_millis();
545 println!("Took {}ms", ms);
546 println!("\tResult: {}", result);
547 println!("\tAfter: {}", overview.visible);
548 }
549 println!();
550
551 print!("\tGetting full conversation... ");
552 let mut stopwatch = StopWatch::start();
553 let mut conversation = overview
554 .get(&account.client, &account.key_pair)
555 .await
556 .unwrap();
557 let ms = stopwatch.split().split.as_millis();
558 println!("Took {}ms", ms);
559 println!("{:#?}", conversation);
560 print!("\tRefreshing conversation... ");
561 let mut stopwatch = StopWatch::start();
562 conversation
563 .refresh(&account.client, &account.key_pair)
564 .await
565 .unwrap();
566 let ms = stopwatch.split().split.as_millis();
567 println!("Took {}ms", ms);
568 }
569
570 if let Ok(reply_number) = env::var("MESSAGES_REPLY_TO") {
571 let reply_number = reply_number.parse::<usize>().unwrap();
572 let overview = overviews.get(reply_number).unwrap();
573 let conversation = overview
574 .get(&account.client, &account.key_pair)
575 .await
576 .unwrap();
577
578 print!("Replying to conversation... ");
579 let mut stopwatch = StopWatch::start();
580 let result = conversation
581 .reply("Test reply", &account.client, &account.key_pair)
582 .await
583 .unwrap();
584 let ms = stopwatch.split().split.as_millis();
585 println!("Took {}ms", ms);
586 assert_eq!(result.is_some(), true);
587 println!("UID of new message: {}", result.unwrap());
588 }
589
590 println!(
591 "Can choose type: {}",
592 can_choose_type(&account.client).await.unwrap()
593 );
594
595 if let Ok(query) = env::var("MESSAGES_RECEIVER_QUERY") {
596 print!("Searching for receiver... ");
597 let mut stopwatch = StopWatch::start();
598 let results = search_receiver(&query, &account.client).await.unwrap();
599 let ms = stopwatch.split().split.as_millis();
600 println!("Took {}ms", ms);
601 println!("Search results: {:#?}", results);
602
603 if let Ok(person_pos) = env::var("MESSAGES_RECEIVER_POS_CREATE") {
604 let person_pos = person_pos.parse::<usize>().unwrap();
605 let content =
606 fs::read_to_string("test_file.txt").unwrap_or("Test Message".to_string());
607 print!("Creating conversation... ");
608 let mut stopwatch = StopWatch::start();
609 let result = create_conversation(
610 &vec![results.get(person_pos).unwrap().to_owned()],
611 "Test Message",
612 &content,
613 &account.client,
614 &account.key_pair,
615 )
616 .await
617 .unwrap();
618 let ms = stopwatch.split().split.as_millis();
619 println!("Took {}ms", ms);
620 assert_eq!(result.is_some(), true);
621 println!("UID of new Conversation: {}", result.unwrap());
622 }
623 }
624
625 println!()
626 }
627
628 #[tokio::test]
629 async fn test_calendar() {
630 let account = create_account().await;
631 match account.is_supported(Feature::Calendar) {
632 true => {
633 println!("Fetching calendar entries...");
634 let mut stopwatch = StopWatch::start();
635 let entries = calendar::get_entries(
636 chrono::Local::now().date_naive(),
637 chrono::Local::now().date_naive() + chrono::Duration::days(365),
638 None,
639 &account.client,
640 )
641 .await
642 .unwrap();
643 let ms = stopwatch.split().split.as_millis();
644 for entry in entries {
645 println!("Entry: {:?}", entry);
646 }
647 println!("Took {}ms", ms);
648
649 let mut stopwatch = StopWatch::start();
650 println!("Downloading exports...");
651 println!(
652 "iCal link: {}",
653 CalendarExports::get_ical(&account.client).await.unwrap()
654 );
655 let exports = CalendarExports::get(&account.client).await.unwrap();
656 exports
657 .get_export(
658 &account.client,
659 CalendarExportFileType::PDF(CalendarExportFileTypePDF::YearDetailed(
660 *exports.available_years.first().unwrap(),
661 )),
662 "./target/temp.pdf",
663 )
664 .await
665 .unwrap();
666 let ms = stopwatch.split().split.as_millis();
667 println!("Took {}ms", ms);
668 }
669 false => {
670 println!("Calendar is not support. Skipping...")
671 }
672 }
673 }
674}