1use std::fmt::Display;
25
26use openssl::symm::{Cipher, Crypter, Mode};
27
28pub struct Msg {
49 title: String,
51
52 body: String,
54
55 level: Option<Level>,
63
64 badge: Option<u64>,
66
67 auto_copy: Option<u8>,
69
70 copy: Option<String>,
72
73 sound: Option<String>,
75
76 icon: Option<String>,
78
79 group: Option<String>,
81
82 is_archive: Option<u8>,
84
85 url: Option<String>,
87
88 iv: Option<String>,
90 enc_type: Option<EncryptType>,
92 mode: Option<EncryptMode>,
94 key: Option<String>,
96 cipher: Option<Cipher>,
98 id: Option<String>,
100 is_deleted: Option<bool>,
102}
103
104
105#[derive(Clone, Copy)]
113pub enum Level {
114 ACTIVE,
115 TIMESENSITIVE,
116 PASSIVE
117}
118
119impl Level {
120 pub fn from_str(str: &str) -> Option<Self> {
121 match str.to_lowercase().as_str() {
122 "timesensitive" => Some(Self::TIMESENSITIVE),
123 "passive" => Some(Self::PASSIVE),
124 "active" => Some(Self::ACTIVE),
125 _ => None
126 }
127 }
128}
129
130impl Display for Level {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 let str =
133 match self {
134 Level::ACTIVE => "active",
135 Level::TIMESENSITIVE => "timeSensitive",
136 Level::PASSIVE =>"passive"
137 };
138 write!(f, "{}", str)
139 }
140}
141
142#[derive(Clone, Copy)]
143pub enum EncryptMode {
144 CBC,
145 ECB,
146 GCM,
147}
148
149impl EncryptMode {
150 pub fn from_str(str: &str) -> Option<Self> {
151 if str.is_empty() {
152 return None;
153 }
154 match str.to_lowercase().as_str() {
155 "cbc" => Some(EncryptMode::CBC),
156 "ecb" => Some(EncryptMode::ECB),
157 "gcm" => Some(EncryptMode::GCM),
158 _ => None,
159 }
160 }
161}
162
163#[derive(Clone, Copy)]
164pub enum EncryptType {
165 AES128,
166 AES192,
167 AES256,
168}
169
170impl EncryptType {
171 pub fn from_str(str: &str) -> Option<Self> {
172 if str.is_empty() {
173 return None;
174 }
175 match str.to_lowercase().as_str() {
176 "aes128" => Some(EncryptType::AES128),
177 "aes192" => Some(EncryptType::AES192),
178 "aes256" => Some(EncryptType::AES256),
179 _ => None,
180 }
181 }
182}
183
184impl Msg {
185 pub fn new(title: &str, body: &str) -> Self {
194 Msg {
195 ..Self::default(Some(title.to_string()), body.to_string())
196 }
197 }
198
199 pub fn with_body(body: &str) -> Self {
207 Msg {
208 ..Self::default(None, body.to_string())
209 }
210 }
211
212 fn default(title: Option<String>, body: String) -> Self {
221 Msg {
222 title: title.unwrap_or("Notification".to_string()),
223 body,
224 level: None,
225 badge: None,
226 auto_copy: None,
227 copy: None,
228 sound: Some("chime.caf".to_string()),
229 icon: Some("https://github.com/66f94eae/bark-dev/raw/main/bot.jpg".to_string()),
230 group: None,
231 is_archive: None,
232 url: None,
233 iv: None,
234 enc_type: None,
235 mode: None,
236 key: None,
237 cipher: None,
238 id: None,
239 is_deleted: None,
240 }
241 }
242
243 pub fn get_id(&self) -> Option<String> {
244 self.id.clone()
245 }
246
247 pub fn is_deleted(&self) -> bool {
248 match self.is_deleted {
249 Some(x) => x == true,
250 None => false
251 }
252 }
253
254 pub fn set_level(&mut self, level: Level) -> &mut Self {
262 self.level = Some(level);
263 self
264 }
265
266 pub fn set_badge(&mut self, badge: u64) -> &mut Self {
274 if badge > 0 {
275 self.badge = Some(badge);
276 } else {
277 self.badge = None;
278 }
279 self
280 }
281
282 pub fn set_auto_copy(&mut self, auto_copy: bool) -> &mut Self {
290 match auto_copy {
291 false => self.auto_copy = Some(0),
292 true => self.auto_copy = None,
293 }
294 self
295 }
296
297 pub fn set_copy(&mut self, copy: &str) -> &mut Self {
305 if copy.trim().is_empty() {
306 self.copy = None;
307 } else {
308 self.copy = Some(copy.to_string());
309 }
310 self
311 }
312
313 pub fn set_sound(&mut self, sound: &str) -> &mut Self {
321 self.sound = Some(sound.to_string());
322 self
323 }
324
325 pub fn set_icon(&mut self, icon: &str) -> &mut Self {
333 if icon.trim().is_empty() {
334 self.icon = None;
335 } else {
336 self.icon = Some(icon.to_string());
337 }
338 self
339 }
340
341 pub fn set_group(&mut self, group: &str) -> &mut Self {
349 self.group = Some(group.to_string());
350 self
351 }
352
353 pub fn set_is_archive(&mut self, is_archive: bool) -> &mut Self {
361 match is_archive {
362 true => self.is_archive = Some(1),
363 false => self.is_archive = None,
364 }
365 self
366 }
367
368 pub fn set_url(&mut self, url: &str) -> &mut Self {
376 if url.trim().is_empty() {
377 self.url = None;
378 } else {
379 self.url = Some(url.to_string());
380 }
381 self
382 }
383
384 pub fn set_iv(&mut self, iv: &str) -> &mut Self {
392 if iv.trim().is_empty() {
393 self.iv = None;
394 } else if iv.len() != 12 {
395 panic!("Invalid IV length. IV must be 12 bytes long.");
396 } else {
397 self.iv = Some(iv.to_string());
398 }
399 self
400 }
401
402 pub fn gen_iv(&mut self) -> &mut Self {
407 let mut iv: [u8; 16] = [0u8; 16];
408 openssl::rand::rand_bytes(&mut iv).unwrap();
409 self.set_iv(iv.iter().map(|b| format!("{:02x}", b)).collect::<String>().split_off(16).as_str())
410 }
411
412 fn set_cipher(&mut self) -> &mut Self {
413 if self.enc_type.is_none() || self.mode.is_none() {
414 return self;
415 }
416 let enc_type = self.enc_type.unwrap();
417 let mode = self.mode.unwrap();
418
419 let cipher: Cipher = match enc_type {
420 EncryptType::AES128 => {
421 match mode {
422 EncryptMode::CBC => {
423 Cipher::aes_128_cbc()
424 },
425 EncryptMode::ECB => {
426 Cipher::aes_128_ecb()
427 },
428 EncryptMode::GCM => {
429 Cipher::aes_128_gcm()
430 },
431 }
432 },
433 EncryptType::AES192 => {
434 match mode {
435 EncryptMode::CBC => {
436 Cipher::aes_192_cbc()
437 },
438 EncryptMode::ECB => {
439 Cipher::aes_192_ecb()
440 },
441 EncryptMode::GCM => {
442 Cipher::aes_192_gcm()
443 },
444 }
445 },
446 EncryptType::AES256 => {
447 match mode {
448 EncryptMode::CBC => {
449 Cipher::aes_256_cbc()
450 },
451 EncryptMode::ECB => {
452 Cipher::aes_256_ecb()
453 },
454 EncryptMode::GCM => {
455 Cipher::aes_256_gcm()
456 },
457 }
458 },
459 };
460 self.cipher = Some(cipher);
461 self
462 }
463
464 pub fn set_enc_type(&mut self, enc_type: EncryptType) -> &mut Self {
475 if self.enc_type.is_some() {
476 panic!("Encrypt type can only be set once");
477 }
478 self.enc_type = Some(enc_type);
479 self.set_cipher();
480 self
481 }
482
483 pub fn set_mode(&mut self, mode: EncryptMode) -> &mut Self {
494 if self.mode.is_some() {
495 panic!("Encrypt mode can only be set once");
496 }
497 self.mode = Some(mode);
498 match mode {
499 EncryptMode::ECB | EncryptMode::GCM => {
500 if self.iv.is_none() {
501 self.gen_iv();
502 }
503 },
504 _ => {},
505 }
506 self.set_cipher();
507 self
508 }
509
510 pub fn set_key(&mut self, key: &str) -> &mut Self {
518 if key.len() != 24 {
519 panic!("Invalid key length. Key must be 24 characters long.");
520 }
521 self.key = Some(key.to_string());
522 self
523 }
524
525 pub fn set_id(&mut self, msg_id: &str) -> &mut Self {
526 if msg_id.as_bytes().len() >= 64 {
527 panic!("Invalid msg_id length.The value of this key must not exceed 64 bytes.");
528 }
529 self.id = Some(msg_id.to_string());
530 self
531 }
532
533 pub fn set_deleted(&mut self) -> &mut Self {
534 self.is_deleted = Some(true);
535 self
536 }
537
538 fn json(&self, encry_body: Option<String>) -> String {
539 let mut body: String = format!("{{\"aps\":{{\"mutable-content\":1,\"category\":\"myNotificationCategory\",\"interruption-level\":\"{level}\",", level = self.level.unwrap_or_else(|| Level::ACTIVE));
540
541 if let Some(badge) = self.badge {
542 body += &format!("\"badge\":{badge},", badge = badge);
543 }
544
545 if let Some(sound) = &self.sound {
546 body += &format!("\"sound\":\"{sound}\",", sound = sound);
547 }
548
549 if let Some(group) = &self.group {
550 body += &format!("\"thread-id\":\"{group}\",", group = group);
551 }
552
553 let alert: String = format!(
554 "\"alert\":{{\"title\":\"{title}\",\"body\":\"{body}\"}}}}",
555 title = self.title,
556 body = if encry_body.is_some() {
557 "NoContent"
558 } else {
559 self.body.as_str()
560 }
561 );
562
563 body = body + &alert;
564
565 if let Some(icon) = &self.icon {
566 body += &format!(",\"icon\":\"{icon}\"", icon = icon);
567 }
568
569 if let Some(auto_copy) = self.auto_copy {
570 body += &format!(",\"autoCopy\":{auto_copy}", auto_copy = auto_copy);
571 }
572
573 if let Some(is_archive) = self.is_archive {
574 body += &format!(",\"isArchive\":{is_archive}", is_archive = is_archive);
575 }
576
577 if let Some(copy) = &self.copy {
578 body += &format!(",\"copy\":\"{copy}\"", copy = copy);
579 }
580
581 if let Some(url) = &self.url {
582 body += &format!(",\"url\":\"{url}\"", url = url);
583 }
584
585 if let Some(iv) = &self.iv {
586 body += &format!(",\"iv\":\"{iv}\"", iv = iv);
587 }
588
589 if let Some(encry_body) = encry_body {
590 body += &format!(",\"ciphertext\":\"{encry_body}\"", encry_body = encry_body);
591 }
592
593 body + "}"
594 }
595
596 fn to_json(&self) -> String {
597 self.json(None)
601 }
602
603 fn encrypt(&self) -> Result<String, Box<dyn std::error::Error>> {
608 if self.enc_type.is_none() || self.mode.is_none() || self.key.is_none() {
609 panic!("Encrypt type, mode, and key must be set");
610 }
611
612 let key: String = self.key.as_ref().unwrap().clone();
613
614 let original: String = format!("{{\"body\":\"{}\"}}", self.body);
615 let original: &[u8] = original.as_bytes();
616
617 let cipher: Cipher = self.cipher.unwrap();
618
619 let mut crypter: Crypter = Crypter::new(
620 cipher,
621 Mode::Encrypt,
622 key.as_bytes(),
623 Some(self.iv.as_ref().unwrap().as_bytes()),
624 )
625 .unwrap();
626 crypter.pad(true); let mut buffer: Vec<u8> = vec![0; original.len() + cipher.block_size()];
628 let count: usize = crypter.update(&original, &mut buffer).unwrap();
629 let rest: usize = crypter.finalize(&mut buffer[count..]).unwrap();
630 buffer.truncate(count + rest);
631 Ok(self.json(Some(openssl::base64::encode_block(&buffer))))
632 }
633
634 pub fn serialize(&self) -> String {
639 if let Some(id) = &self.id {
640 if self.is_deleted() {
641 return format!("{{\"aps\":{{\"content-available\":1}},\"delete\":\"1\",\"id\":\"{id}\"}}");
642 }
643 }
644 if self.cipher.is_some() {
645 match self.encrypt() {
646 Ok(encrypted) => encrypted,
647 Err(e) => panic!("Error encrypting message: {}", e),
648 }
649 } else {
650 self.to_json()
651 }
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658
659 #[test]
660 fn test_to_json_all_field() {
661 let mut msg = Msg::new("Test Title", "Test Body");
662 msg.set_level(Level::TIMESENSITIVE);
663 msg.set_badge(1);
664 msg.set_auto_copy(true);
665 msg.set_copy("Test Copy");
666 msg.set_sound("chime.caf");
667 msg.set_icon("icon.png");
668 msg.set_group("Test Group");
669 msg.set_is_archive(true);
670 msg.set_url("https://example.com");
671 let json = msg.to_json();
672 println!("{}", json);
673 assert_eq!(json, "{\"aps\":{\"mutable-content\":1,\"category\":\"myNotificationCategory\",\"interruption-level\":\"timeSensitive\",\"badge\":1,\"sound\":\"chime.caf\",\"thread-id\":\"Test Group\",\"alert\":{\"title\":\"Test Title\",\"body\":\"Test Body\"}},\"icon\":\"icon.png\",\"isArchive\":1,\"copy\":\"Test Copy\",\"url\":\"https://example.com\"}");
674 }
675
676 #[test]
677 fn test_to_json_part_field() {
678 let mut msg = Msg::new("Test Title", "Test Body");
679 msg.set_level(Level::PASSIVE);
680 msg.set_badge(1);
681 msg.set_auto_copy(true);
682 msg.set_copy("");
683 msg.set_sound("chime.caf");
684 msg.set_icon("icon.png");
685 let json = msg.to_json();
686 println!("{}", json);
687 assert_eq!(json, "{\"aps\":{\"mutable-content\":1,\"category\":\"myNotificationCategory\",\"interruption-level\":\"passive\",\"badge\":1,\"sound\":\"chime.caf\",\"alert\":{\"title\":\"Test Title\",\"body\":\"Test Body\"}},\"icon\":\"icon.png\"}");
688 }
689
690 #[test]
691 fn test_to_json_default() {
692 let msg = Msg::new("Test Title", "Test Body");
693 let json = msg.to_json();
694 println!("{}", json);
695 assert_eq!(json, "{\"aps\":{\"mutable-content\":1,\"category\":\"myNotificationCategory\",\"interruption-level\":\"active\",\"sound\":\"chime.caf\",\"alert\":{\"title\":\"Test Title\",\"body\":\"Test Body\"}},\"icon\":\"https://github.com/66f94eae/bark-dev/raw/main/bot.jpg\"}");
696 }
697}