-
Notifications
You must be signed in to change notification settings - Fork 2
/
icloud_backup.py
205 lines (175 loc) · 9.09 KB
/
icloud_backup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import argparse
import os
import configparser
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudAPIResponseException
from termcolor import colored
import requests
# Diccionario global para almacenar estadísticas
stats = {
'items_count': 0,
'photos_backed_up': 0,
'videos_backed_up': 0,
'videos_deleted': 0,
'recently_deleted_emptied': False
}
VIDEO_EXTENSIONS = ['.mp4', '.mov', '.avi', '.mkv']
# Función para enviar un mensaje a través de Telegram
def send_telegram_message(token, chat_id, text):
"""Send a message to a Telegram user or group."""
BASE_URL = f"https://api.telegram.org/bot{token}/sendMessage"
payload = {
'chat_id': chat_id,
'text': text,
'parse_mode': 'Markdown'
}
response = requests.post(BASE_URL, data=payload)
return response.json()
# Función para obtener credenciales de iCloud desde un archivo de configuración
def get_icloud_credentials_from_config(filename):
"""Retrieve iCloud credentials from a config file."""
print(colored(f"🔧 Leyendo configuración desde {filename}...", "cyan"))
config = configparser.ConfigParser()
config.read(filename)
return config['ICloud']['username'], config['ICloud']['password']
# Función para autenticarse en iCloud
def authenticate(username, password):
"""Authenticate with iCloud."""
print(colored("🔐 Iniciando autenticación con iCloud...", "cyan"))
api = PyiCloudService(username, password)
handle_2fa(api)
print(colored("🟢 Autenticación exitosa.", "green"))
return api
# Función para manejar la autenticación de dos factores
def handle_2fa(api):
"""Handle two-factor authentication if needed."""
if api.requires_2fa:
print(colored("🟡 Se requiere autenticación de dos factores.", "yellow"))
code = input("Introduce el código que recibiste en uno de tus dispositivos: ")
if not api.validate_2fa_code(code):
print(colored("🔴 Error al verificar el código de seguridad.", "red"))
exit(1)
# Función para verificar si un archivo es un video según su extensión
def is_video_file(filename):
"""Check if a given file is a video based on its extension."""
_, ext = os.path.splitext(filename)
return ext.lower() in VIDEO_EXTENSIONS
# Función para vaciar el álbum 'Borrados recientemente' en iCloud
def empty_recently_deleted(api):
"""Empty the 'Recently Deleted' album."""
print(colored("🔍 Vaciando el álbum 'Borrados recientemente'...", "cyan"))
recently_deleted = api.photos.albums.get('Recently Deleted')
for item in recently_deleted:
item.delete()
print(colored("🟢 Álbum 'Borrados recientemente' vaciado.", "green"))
stats['recently_deleted_emptied'] = True
# Función para realizar copia de seguridad de fotos a un directorio local
def backup_photos_to_local(api, album_name, dest_directory):
"""Backup photos to a local directory."""
print(colored(f"🔍 Buscando el álbum '{album_name}'...", "cyan"))
if not os.path.exists(dest_directory):
print(colored(f"📂 Creando directorio de destino: {dest_directory}", "yellow"))
os.makedirs(dest_directory)
album_obj = api.photos.albums.get(album_name)
if not album_obj:
print(colored(f"🔴 No se encontró el álbum '{album_name}'.", "red"))
return
existing_photos = set(os.listdir(dest_directory))
photos_to_backup = []
checked_photos_count = 0
for photo in album_obj.photos:
checked_photos_count += 1
if photo.filename not in existing_photos:
photos_to_backup.append(photo)
print(f"🔍 Verificando fotos: {checked_photos_count}", end='\r')
stats['items_count'] = checked_photos_count
print(colored(f"\n🟢 Checks finished: {checked_photos_count - len(photos_to_backup)}/{checked_photos_count}", "green"))
print(colored(f"🔵 Fotos o vídeos a realizar backup: {len(photos_to_backup)}", "cyan"))
for index, photo in enumerate(photos_to_backup, start=1):
print(f"⏳ Realizando backup {index}/{len(photos_to_backup)}...", end='\r')
try:
file_path = os.path.join(dest_directory, photo.filename)
with open(file_path, 'wb') as file:
file.write(photo.download().raw.read())
if is_video_file(photo.filename):
stats['videos_backed_up'] += 1
else:
stats['photos_backed_up'] += 1
except PyiCloudAPIResponseException:
print(colored(f"\n🔴 Error al descargar {photo.filename}. Omitiendo...", "red"))
print(colored("\n🟢 Backup completado.", "green"))
# Función para eliminar videos de iCloud que ya han sido respaldados localmente
def delete_backed_up_videos(api, album_name, dest_directory):
"""Delete videos from iCloud that have already been backed up locally."""
print(colored(f"🔍 Buscando videos en el álbum '{album_name}' para eliminar...", "cyan"))
album_obj = api.photos.albums.get(album_name)
if not album_obj:
print(colored(f"🔴 No se encontró el álbum '{album_name}'.", "red"))
return
existing_files = set(os.listdir(dest_directory))
deleted_count = 0
for video in album_obj.photos:
if video.filename in existing_files and is_video_file(video.filename):
try:
video.delete()
deleted_count += 1
stats['videos_deleted'] += 1
print(colored(f"⏳ Eliminando video {deleted_count}: {video.filename} de iCloud...", "yellow"), end='\r')
except PyiCloudAPIResponseException:
print(colored(f"\n🔴 Error al eliminar {video.filename} de iCloud. Omitiendo...", "red"))
print(colored(f"\n🟢 Eliminados {deleted_count} videos de iCloud.","green"))
# Función para mostrar un resumen de las acciones tomadas
def display_summary():
"""Display a summary of actions taken."""
print("\n" + colored("📊 Resumen:", "blue"))
print(colored(f"🔵 Items totales: {stats['items_count']}", "blue"))
print(colored(f"🟢 Fotos realizadas backup: {stats['photos_backed_up']}", "green"))
print(colored(f"🟢 Vídeos realizados backup: {stats['videos_backed_up']}", "green"))
print(colored(f"🔴 Vídeos eliminados de iCloud: {stats['videos_deleted']}", "red"))
if stats['recently_deleted_emptied']:
print(colored("🟢 Álbum 'Borrados recientemente' vaciado.", "green"))
# Función para generar un mensaje de resumen para la notificación de Telegram
def generate_summary_message():
"""Generate a summary message for the Telegram notification."""
message = "📊 Resumen:\n\n"
message += f"🟢 Items totales: {stats['items_count']}\n"
message += f"🟢 Fotos realizadas backup: {stats['photos_backed_up']}\n"
message += f"🟢 Vídeos realizados backup: {stats['videos_backed_up']}\n"
message += f"🔴 Vídeos eliminados de iCloud: {stats['videos_deleted']}\n"
message += f"🟢 Álbum 'Borrados recientemente' vaciado: {'Sí' if stats['recently_deleted_emptied'] else 'No'}\n"
return message
# Función principal
def main():
parser = argparse.ArgumentParser(description='Herramienta de backup de fotos y videos de iCloud')
# Argumentos existentes
parser.add_argument('--config', default='/script-backup/config.ini', help='Archivo de configuración (por defecto: "/script-backup/config.ini")')
parser.add_argument('--album', default='All Photos', help='Nombre del álbum de iCloud (por defecto: "All Photos")')
parser.add_argument('--destination', default='/backup-icloud', help='Directorio de destino para el backup (por defecto: "/backup-icloud")')
parser.add_argument('--delete-videos', action='store_true', help='Eliminar videos de iCloud después del backup')
# Nuevo argumento para controlar el envío de mensajes de Telegram
parser.add_argument('--send-telegram', action='store_true', help='Enviar un mensaje de Telegram al finalizar (por defecto: no se envía)')
args = parser.parse_args()
# Información de Telegram (configura estas variables con tus propios valores)
TELEGRAM_TOKEN = 'TU_TOKEN_DE_TELEGRAM'
TELEGRAM_CHAT_ID = 'TU_CHAT_ID_DE_TELEGRAM'
try:
username, password = get_icloud_credentials_from_config(args.config)
api = authenticate(username, password)
backup_photos_to_local(api, args.album, args.destination)
if args.delete_videos:
delete_backed_up_videos(api, args.album, args.destination)
empty_recently_deleted(api)
summary_message = generate_summary_message()
# Enviar un mensaje de Telegram si el argumento --send-telegram es usado
if args.send_telegram:
send_telegram_message(TELEGRAM_TOKEN, TELEGRAM_CHAT_ID, summary_message + "\n🟢 Proceso completado exitosamente.")
else:
print(summary_message + "\n🟢 Proceso completado exitosamente.")
except Exception as e:
error_message = f"🔴 Hubo un error: {str(e)}"
if args.send_telegram:
send_telegram_message(TELEGRAM_TOKEN, TELEGRAM_CHAT_ID, error_message)
else:
print(error_message)
if __name__ == "__main__":
main()