diff --git a/test-coverage/index.html b/test-coverage/index.html index 0d8275486..cb8605aa3 100644 --- a/test-coverage/index.html +++ b/test-coverage/index.html @@ -53,10 +53,10 @@

Coverage report: Total - 825 + 863 0 0 - 100% + 100% @@ -69,10 +69,10 @@

Coverage report: src/config_parser.py - 198 + 212 0 0 - 100% + 100% src/email_message.py @@ -83,10 +83,10 @@

Coverage report: src/notify.py - 44 + 68 0 0 - 100% + 100% src/sync.py @@ -126,7 +126,7 @@

Coverage report:

coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src___init___py.html b/test-coverage/src___init___py.html index 429d2ee0a..d52a45ddc 100644 --- a/test-coverage/src___init___py.html +++ b/test-coverage/src___init___py.html @@ -195,7 +195,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src_config_parser_py.html b/test-coverage/src_config_parser_py.html index 02e0d7be7..e88a7ccfd 100644 --- a/test-coverage/src_config_parser_py.html +++ b/test-coverage/src_config_parser_py.html @@ -22,8 +22,8 @@

Coverage for src/config_parser.py :

Show keyboard shortcuts

- 198 statements   - + 212 statements   +

@@ -446,12 +446,38 @@

392 fmt = get_config_value(config=config, config_path=config_path) 

393 LOGGER.info(f"Using format {fmt}.") 

394 return fmt 

+

395 

+

396 

+

397def get_telegram_bot_token(config): 

+

398 """Return telegram bot token from config.""" 

+

399 bot_token = None 

+

400 config_path = ["app", "telegram", "bot_token"] 

+

401 if not traverse_config_path(config=config, config_path=config_path): 

+

402 LOGGER.warning( 

+

403 f"Warning: bot_token is not found in {config_path_to_string(config_path)}." 

+

404 ) 

+

405 else: 

+

406 bot_token = get_config_value(config=config, config_path=config_path) 

+

407 return bot_token 

+

408 

+

409 

+

410def get_telegram_chat_id(config): 

+

411 """Return telegram chat id from config.""" 

+

412 chat_id = None 

+

413 config_path = ["app", "telegram", "chat_id"] 

+

414 if not traverse_config_path(config=config, config_path=config_path): 

+

415 LOGGER.warning( 

+

416 f"Warning: chat_id is not found in {config_path_to_string(config_path)}." 

+

417 ) 

+

418 else: 

+

419 chat_id = get_config_value(config=config, config_path=config_path) 

+

420 return chat_id 

diff --git a/test-coverage/src_email_message_py.html b/test-coverage/src_email_message_py.html index ae09d87f6..02d10b5ee 100644 --- a/test-coverage/src_email_message_py.html +++ b/test-coverage/src_email_message_py.html @@ -108,7 +108,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src_notify_py.html b/test-coverage/src_notify_py.html index 0e31ccacb..549ccd8dc 100644 --- a/test-coverage/src_notify_py.html +++ b/test-coverage/src_notify_py.html @@ -22,8 +22,8 @@

Coverage for src/notify.py :

Show keyboard shortcuts

- 44 statements   - + 68 statements   +

@@ -56,70 +56,115 @@

2import datetime 

3import smtplib 

4 

-

5from src import LOGGER, config_parser 

-

6from src.email_message import EmailMessage as Message 

-

7 

-

8 

-

9def send(config, last_send=None, dry_run=False): 

-

10 """Send email.""" 

-

11 sent_on = None 

-

12 email = config_parser.get_smtp_email(config=config) 

-

13 to_email = config_parser.get_smtp_to_email(config=config) 

-

14 host = config_parser.get_smtp_host(config=config) 

-

15 port = config_parser.get_smtp_port(config=config) 

-

16 no_tls = config_parser.get_smtp_no_tls(config=config) 

-

17 username = config_parser.get_smtp_username(config=config) 

-

18 password = config_parser.get_smtp_password(config=config) 

-

19 

-

20 if last_send and last_send > datetime.datetime.now() - datetime.timedelta(hours=24): 

-

21 LOGGER.info("Throttling email to once a day") 

-

22 sent_on = last_send 

-

23 elif email and host and port: 

-

24 try: 

-

25 sent_on = datetime.datetime.now() 

-

26 if not dry_run: 

-

27 smtp = smtplib.SMTP(host, port) 

-

28 smtp.set_debuglevel(0) 

-

29 smtp.connect(host, port) 

-

30 if not no_tls: 

-

31 smtp.starttls() 

-

32 

-

33 if password: 

-

34 if username: 

-

35 smtp.login(username, password) 

-

36 else: 

-

37 smtp.login(email, password) 

+

5import requests 

+

6 

+

7from src import LOGGER, config_parser 

+

8from src.email_message import EmailMessage as Message 

+

9 

+

10 

+

11def notify_telegram(config, last_send=None, dry_run=False): 

+

12 """Send telegram notification.""" 

+

13 sent_on = None 

+

14 bot_token = config_parser.get_telegram_bot_token(config=config) 

+

15 chat_id = config_parser.get_telegram_chat_id(config=config) 

+

16 

+

17 if last_send and last_send > datetime.datetime.now() - datetime.timedelta(hours=24): 

+

18 LOGGER.info("Throttling telegram to once a day") 

+

19 sent_on = last_send 

+

20 elif bot_token and chat_id: 

+

21 sent_on = datetime.datetime.now() 

+

22 if not dry_run: 

+

23 # Post message to telegram bot using API 

+

24 if not post_message_to_telegram( 

+

25 bot_token, 

+

26 chat_id, 

+

27 """Two-step authentication for iCloud Drive, Photos (Docker) is required. 

+

28 Please login to your server and authenticate. Please run - 

+

29 `docker exec -it icloud /bin/sh -c "icloud --username=<icloud-username> 

+

30 --session-directory=/app/session_data"`.""", 

+

31 ): 

+

32 sent_on = None 

+

33 else: 

+

34 LOGGER.warning( 

+

35 "Not sending 2FA notification because Telegram is not configured." 

+

36 ) 

+

37 return sent_on 

38 

-

39 msg = build_message(email, to_email) 

-

40 

-

41 smtp.sendmail(from_addr=email, to_addrs=to_email, msg=msg.as_string()) 

-

42 smtp.quit() 

-

43 except Exception as e: 

-

44 sent_on = None 

-

45 LOGGER.error(f"Failed to send email: {str(e)}.") 

-

46 else: 

-

47 LOGGER.warning("Not sending 2FA notification because SMTP is not configured") 

-

48 

-

49 return sent_on 

-

50 

+

39 

+

40def post_message_to_telegram(bot_token, chat_id, message): 

+

41 """Post message to telegram bot using API.""" 

+

42 url = f"https://api.telegram.org/bot{bot_token}/sendMessage" 

+

43 params = {"chat_id": chat_id, "text": message} 

+

44 response = requests.post(url, params=params) 

+

45 if response.status_code == 200: 

+

46 return True 

+

47 else: 

+

48 # Log error message 

+

49 LOGGER.error(f"Failed to send telegram notification. Response: {response.text}") 

+

50 return False 

51 

-

52def build_message(email, to_email): 

-

53 """Create email message.""" 

-

54 message = Message(to=to_email) 

-

55 message.sender = "icloud-docker <" + email + ">" 

-

56 message.date = datetime.datetime.now().strftime("%d/%m/%Y %H:%M") 

-

57 message.subject = "icloud-docker: Two step authentication required" 

-

58 message.body = """Two-step authentication for iCloud Drive, Photos (Docker) is required. 

-

59Please login to your server and authenticate. Please run - 

-

60`docker exec -it icloud /bin/sh -c "icloud --username=<icloud-username> --session-directory=/app/session_data"`.""" 

-

61 

-

62 return message 

+

52 

+

53def send(config, last_send=None, dry_run=False): 

+

54 """Send notifications.""" 

+

55 sent_on = None 

+

56 notify_telegram(config=config, last_send=last_send, dry_run=dry_run) 

+

57 email = config_parser.get_smtp_email(config=config) 

+

58 to_email = config_parser.get_smtp_to_email(config=config) 

+

59 host = config_parser.get_smtp_host(config=config) 

+

60 port = config_parser.get_smtp_port(config=config) 

+

61 no_tls = config_parser.get_smtp_no_tls(config=config) 

+

62 username = config_parser.get_smtp_username(config=config) 

+

63 password = config_parser.get_smtp_password(config=config) 

+

64 

+

65 if last_send and last_send > datetime.datetime.now() - datetime.timedelta(hours=24): 

+

66 LOGGER.info("Throttling email to once a day") 

+

67 sent_on = last_send 

+

68 elif email and host and port: 

+

69 try: 

+

70 sent_on = datetime.datetime.now() 

+

71 if not dry_run: 

+

72 smtp = smtplib.SMTP(host, port) 

+

73 smtp.set_debuglevel(0) 

+

74 smtp.connect(host, port) 

+

75 if not no_tls: 

+

76 smtp.starttls() 

+

77 

+

78 if password: 

+

79 if username: 

+

80 smtp.login(username, password) 

+

81 else: 

+

82 smtp.login(email, password) 

+

83 

+

84 msg = build_message(email, to_email) 

+

85 

+

86 smtp.sendmail(from_addr=email, to_addrs=to_email, msg=msg.as_string()) 

+

87 smtp.quit() 

+

88 except Exception as e: 

+

89 sent_on = None 

+

90 LOGGER.error(f"Failed to send email: {str(e)}.") 

+

91 else: 

+

92 LOGGER.warning("Not sending 2FA notification because SMTP is not configured") 

+

93 

+

94 return sent_on 

+

95 

+

96 

+

97def build_message(email, to_email): 

+

98 """Create email message.""" 

+

99 message = Message(to=to_email) 

+

100 message.sender = "icloud-docker <" + email + ">" 

+

101 message.date = datetime.datetime.now().strftime("%d/%m/%Y %H:%M") 

+

102 message.subject = "icloud-docker: Two step authentication required" 

+

103 message.body = """Two-step authentication for iCloud Drive, Photos (Docker) is required. 

+

104Please login to your server and authenticate. Please run - 

+

105`docker exec -it icloud /bin/sh -c "icloud --username=<icloud-username> --session-directory=/app/session_data"`.""" 

+

106 

+

107 return message 

diff --git a/test-coverage/src_sync_drive_py.html b/test-coverage/src_sync_drive_py.html index 52c7de8c6..a114d5312 100644 --- a/test-coverage/src_sync_drive_py.html +++ b/test-coverage/src_sync_drive_py.html @@ -397,7 +397,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src_sync_photos_py.html b/test-coverage/src_sync_photos_py.html index 7ccd930f5..e15bcac6f 100644 --- a/test-coverage/src_sync_photos_py.html +++ b/test-coverage/src_sync_photos_py.html @@ -332,7 +332,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src_sync_py.html b/test-coverage/src_sync_py.html index 7e8cf1217..c6a43bbb9 100644 --- a/test-coverage/src_sync_py.html +++ b/test-coverage/src_sync_py.html @@ -211,7 +211,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/src_usage_py.html b/test-coverage/src_usage_py.html index cda62e192..66ec8dfd1 100644 --- a/test-coverage/src_usage_py.html +++ b/test-coverage/src_usage_py.html @@ -189,7 +189,7 @@

« index     coverage.py v5.4, - created at 2023-12-01 18:24 +0000 + created at 2024-01-22 06:37 +0000

diff --git a/test-coverage/status.json b/test-coverage/status.json index 27cda3149..3e15d260f 100644 --- a/test-coverage/status.json +++ b/test-coverage/status.json @@ -1 +1 @@ -{"format":2,"version":"5.4","globals":"39d500336f42ed1e3d49ba0d1ecd0eea","files":{"src___init___py":{"hash":"dccf7a238b1c0ad52f88cf7a87255a24","index":{"nums":[1,77,0,0,0,0,0],"html_filename":"src___init___py.html","relative_filename":"src/__init__.py"}},"src_config_parser_py":{"hash":"c785463cb6c10a25b0cfaacedb7e0e29","index":{"nums":[1,198,0,0,0,0,0],"html_filename":"src_config_parser_py.html","relative_filename":"src/config_parser.py"}},"src_email_message_py":{"hash":"f1cd58e66273dace4abeac30f6057ce9","index":{"nums":[1,33,0,0,0,0,0],"html_filename":"src_email_message_py.html","relative_filename":"src/email_message.py"}},"src_notify_py":{"hash":"e1a386d462494bd45836a96cd952af18","index":{"nums":[1,44,0,0,0,0,0],"html_filename":"src_notify_py.html","relative_filename":"src/notify.py"}},"src_sync_py":{"hash":"a3fb47615530c1d66804f1d01aeabebe","index":{"nums":[1,78,0,0,0,0,0],"html_filename":"src_sync_py.html","relative_filename":"src/sync.py"}},"src_sync_drive_py":{"hash":"4ae7f2dd147f208e3bdd1f1381880ba7","index":{"nums":[1,197,0,0,0,0,0],"html_filename":"src_sync_drive_py.html","relative_filename":"src/sync_drive.py"}},"src_sync_photos_py":{"hash":"4c532cd984a51dc8433ef4c99ff3acb1","index":{"nums":[1,112,0,0,0,0,0],"html_filename":"src_sync_photos_py.html","relative_filename":"src/sync_photos.py"}},"src_usage_py":{"hash":"a9d2ca932b8d5f40998bd6aaf25b232d","index":{"nums":[1,86,0,0,0,0,0],"html_filename":"src_usage_py.html","relative_filename":"src/usage.py"}}}} \ No newline at end of file +{"format":2,"version":"5.4","globals":"39d500336f42ed1e3d49ba0d1ecd0eea","files":{"src___init___py":{"hash":"dccf7a238b1c0ad52f88cf7a87255a24","index":{"nums":[1,77,0,0,0,0,0],"html_filename":"src___init___py.html","relative_filename":"src/__init__.py"}},"src_config_parser_py":{"hash":"4873760913c2aa88e15e2107375840fd","index":{"nums":[1,212,0,0,0,0,0],"html_filename":"src_config_parser_py.html","relative_filename":"src/config_parser.py"}},"src_email_message_py":{"hash":"f1cd58e66273dace4abeac30f6057ce9","index":{"nums":[1,33,0,0,0,0,0],"html_filename":"src_email_message_py.html","relative_filename":"src/email_message.py"}},"src_notify_py":{"hash":"3d8bb4b8aeb384aae324bbea133b3460","index":{"nums":[1,68,0,0,0,0,0],"html_filename":"src_notify_py.html","relative_filename":"src/notify.py"}},"src_sync_py":{"hash":"a3fb47615530c1d66804f1d01aeabebe","index":{"nums":[1,78,0,0,0,0,0],"html_filename":"src_sync_py.html","relative_filename":"src/sync.py"}},"src_sync_drive_py":{"hash":"4ae7f2dd147f208e3bdd1f1381880ba7","index":{"nums":[1,197,0,0,0,0,0],"html_filename":"src_sync_drive_py.html","relative_filename":"src/sync_drive.py"}},"src_sync_photos_py":{"hash":"4c532cd984a51dc8433ef4c99ff3acb1","index":{"nums":[1,112,0,0,0,0,0],"html_filename":"src_sync_photos_py.html","relative_filename":"src/sync_photos.py"}},"src_usage_py":{"hash":"a9d2ca932b8d5f40998bd6aaf25b232d","index":{"nums":[1,86,0,0,0,0,0],"html_filename":"src_usage_py.html","relative_filename":"src/usage.py"}}}} \ No newline at end of file