Backup de directorio usando Telegram con Bot

Respaldar archivo con Bot de Telegram

El día de hoy te enseñaré a respaldar un archivo o directorio y enviarlo a Telegram, de modo que puedas tener un respaldo o backup de información en la nube de Telegram de manera programada usando un Bot.

Respaldar archivo con Telegram - Modo de uso de Bot para hacer backup
Respaldar archivo con Telegram – Modo de uso de Bot para hacer backup

Con el programa que vamos a ver serás capaz de:

  • Enviar un archivo a cualquier usuario, grupo o canal de Telegram a nombre de un Bot
  • En caso de que quieras hacer un respaldo de un directorio en Telegram, el directorio será comprimido en un zip
  • Si el archivo pesa más que el límite, será dividido en varias partes y enviado a Telegram, así que no hay límite de tamaño

Así que dicho con otras palabras vas a ser capaz de respaldar información en Telegram usando un Bot, sin importar el tamaño o contenido del directorio. Más adelante puedes combinar esto con algo como el Cron para Golang y hacer respaldos periódicos.

¿Cómo funciona el respaldo en Telegram?

Para usar la nube de Telegram para almacenar backup de información podemos usar un Bot. Los Bots de Telegram tienen la capacidad de enviar documentos a cualquier usuario, grupo o canal a partir del id del destinatario.

Así que lo que vamos a hacer es programar un Bot de Telegram con Go (aunque podría estar escrito en cualquier lenguaje) que va a leer un archivo o directorio y lo va a enviar al remitente que nosotros hayamos indicado.

Lo bueno de esto es que es totalmente gratis, solo debes obtener un Token hablando con el BotFather. Te voy a mostrar el código fuente completo para que puedas compilarlo por ti mismo así como la aplicación ya compilada y lista para usar pasándole argumentos a través de la línea de comandos.

Enviar un archivo a Telegram

Comencemos revisando la función más simple que envía un documento (sin sobrepasar el límite) a Telegram. El código consume la API HTTP directamente, no usa librerías adicionales. La función queda así:

func enviarUnArchivo(ubicacion string, token string, idChat string) error {
	file, err := os.Open(ubicacion)
	if err != nil {
		return err
	}
	defer file.Close()

	var requestBody bytes.Buffer
	writer := multipart.NewWriter(&requestBody)
	part, err := writer.CreateFormFile("document", path.Base(ubicacion))
	if err != nil {
		return err
	}
	_, err = io.Copy(part, file)
	if err != nil {
		return err
	}

	err = writer.WriteField("chat_id", idChat)
	if err != nil {
		return err
	}

	writer.Close()

	clienteHttp := &http.Client{}
	url := "https://api.telegram.org/bot" + token + "/sendDocument"
	peticion, err := http.NewRequest("POST", url, &requestBody)
	peticion.Header.Set("Content-Type", writer.FormDataContentType())
	if err != nil {
		return err
	}
	respuesta, err := clienteHttp.Do(peticion)
	if err != nil {
		return err
	}
	return manejarRespuestaDeTelegram(respuesta)
}

Para respaldar un archivo en Telegram debemos enviar un documento al endpoint de la API de Bots invocando al método sendDocument. El documento debe ir codificado como un multipart/form-data y debe estar presente en la clave document.

Podemos agregar claves adicionales al formulario; en este caso yo lo estoy haciendo con WriteField para indicar el chat_id que será el Id del chat al que será enviado el respaldo.

Observa que esta función solo será invocada cuando el tamaño del documento no sea mayor al límite. Y con “documento” me refiero al documento que vamos a respaldar o al zip resultante del directorio que queremos enviar a Telegram para respaldarlo.

Respaldar directorio en Telegram

Backup de directorio usando Telegram con Bot
Backup de directorio usando Telegram con Bot

Veamos otro caso: hacer un backup de un directorio en Telegram. En este caso lo más simple es crear un ZIP a partir del directorio sin incluir directorios hijos recursivamente. Podemos hacerlo con la siguiente función de  Go:

func agregarArchivoAZip(escritorZip *zip.Writer, ubicacionArchivo, ubicacionBase string) error {
	archivoParaAgregarAlZip, err := os.Open(ubicacionArchivo)
	if err != nil {
		return err
	}
	defer archivoParaAgregarAlZip.Close()

	informacionDelArchivoQueSeAgrega, err := archivoParaAgregarAlZip.Stat()
	if err != nil {
		return err
	}

	encabezadoArchivoQueSeAgrega, err := zip.FileInfoHeader(informacionDelArchivoQueSeAgrega)
	if err != nil {
		return err
	}

	encabezadoArchivoQueSeAgrega.Name, err = filepath.Rel(ubicacionBase, ubicacionArchivo)
	if err != nil {
		return err
	}

	encabezadoArchivoQueSeAgrega.Method = zip.Deflate

	escritorArchivo, err := escritorZip.CreateHeader(encabezadoArchivoQueSeAgrega)
	if err != nil {
		return err
	}
	_, err = io.Copy(escritorArchivo, archivoParaAgregarAlZip)
	return err
}

func crearZipDeDirectorio(ubicacionDirectorio, nombreArchivoZip string) error {
	archivoZip, err := os.Create(nombreArchivoZip)
	if err != nil {
		return err
	}
	defer archivoZip.Close()
	escritorZip := zip.NewWriter(archivoZip)
	defer escritorZip.Close()
	return filepath.WalkDir(ubicacionDirectorio, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !d.IsDir() {
			return agregarArchivoAZip(escritorZip, path, ubicacionDirectorio)
		}
		return nil
	})
}

Hasta este punto tenemos las funciones para enviar un archivo o enviar un directorio a partir de un zip. Suponiendo que el tamaño se encuentra por debajo del límite, lo enviamos así:

if informacionArchivoParaRespaldar.Size() <= MaximoTamañoArchivoTelegram {
	mensaje += ("El zip resultante pesa menos que el límite\n")
	err = enviarMensaje(mensaje)
	if err != nil {
		return err
	}
	err = enviarUnArchivo(salida)
	if err != nil {
		return err
	}
	return eliminarVariosArchivos([]string{salida})
}

Con eso habremos respaldado un archivo en la nube de Telegram automáticamente con un Bot, pero debemos tomar en cuenta los límites. Para ello, en caso de que el archivo a respaldar sobrepase el límite de tamaño máximo de los Bots de Telegram vamos a dividirlo en varias partes.

Dividir archivo en caso de ser necesario

Ya tenemos un zip de un directorio o un archivo listo para ser enviado a Telegram, pero su tamaño es mayor al límite. En ese caso separamos el archivo en varias partes, en donde cada parte va a tener el tamaño máximo permitido:

func separarArchivoEnVariasPartes(ubicacionArchivoOriginal string, tamañoDeFragmento int64) ([]string, error) {
	var ubicaciones []string
	archivoOriginal, err := os.Open(ubicacionArchivoOriginal)
	if err != nil {
		return ubicaciones, err
	}
	defer archivoOriginal.Close()
	informacionArchivoOriginal, err := archivoOriginal.Stat()
	if err != nil {
		return ubicaciones, err
	}
	tamañoArchivoOriginal := informacionArchivoOriginal.Size()
	cantidadDeFragmentos := (tamañoArchivoOriginal + tamañoDeFragmento - 1) / tamañoDeFragmento

	for i := int64(0); i < cantidadDeFragmentos; i++ {
		nombreFragmento := fmt.Sprintf("%s.part%d", ubicacionArchivoOriginal, i+1)
		ubicaciones = append(ubicaciones, nombreFragmento)
		fragmentoDeArchivoOriginal, err := os.Create(nombreFragmento)
		if err != nil {
			return ubicaciones, err
		}
		_, err = io.CopyN(fragmentoDeArchivoOriginal, archivoOriginal, tamañoDeFragmento)
		if err != nil && err != io.EOF {
			fragmentoDeArchivoOriginal.Close()
			return ubicaciones, err
		}
		fragmentoDeArchivoOriginal.Close()
	}
	return ubicaciones, nil
}

Aquí estamos dividiendo un archivo existente a partir de su ruta y escribiendo varias partes. Toma en cuenta que no es como dividir un zip, se está dividiendo el archivo sin importar su formato o contenido, cortando los bytes como sea necesario gracias a la función io.CopyN copiando solo determinado número de bytes.

Más adelante podemos unir esos archivos con cualquier programa o usando el navegador web con el script que programé para unir archivos con JavaScript.

Respaldar archivo con Telegram

Finalmente llegamos a la función que recibe la ubicación de un archivo o directorio como cadena y se encarga de respaldarlo siguiendo el siguiente proceso:

  1. Si el archivo al que se le hace backup con Telegram pesa menos que el límite, se envía sin más
  2. En caso de que el archivo para respaldar con el Bot de Telegram pese más que el límite o de que se quiera respaldar un directorio, se creará un zip
  3. Después, se comprobará el tamaño del zip. Si el zip pesa más que el máximo tamaño permitido por la API de Telegram entonces se dividirá en varias partes y será enviado en varios mensajes

La función queda así:

func respaldar(ubicacion string, token string, idChat string) error {
	salida := "salida.zip"
	mensaje := fmt.Sprintf("Respaldando <b>%s</b>\n", ubicacion)
	informacionDeArchivoODirectorioParaRespaldar, err := os.Stat(ubicacion)
	if err != nil {
		return err
	}
	if informacionDeArchivoODirectorioParaRespaldar.IsDir() {
		mensaje += "Es un directorio, creando zip...\n"
		err = crearZipDeDirectorio(ubicacion, salida)
	} else {
		if informacionDeArchivoODirectorioParaRespaldar.Size() <= MaximoTamañoArchivoTelegram {
			mensaje += ("Es un archivo que pesa menos que el límite\n")
			err = enviarMensaje(mensaje, token, idChat)
			if err != nil {
				return err
			}
			return enviarUnArchivo(ubicacion, token, idChat)
		}
		err = crearZip(ubicacion, salida)
	}
	if err != nil {
		return err
	}
	informacionArchivoParaRespaldar, err := os.Stat(salida)
	if err != nil {
		return err
	}
	if informacionArchivoParaRespaldar.Size() <= MaximoTamañoArchivoTelegram {
		mensaje += ("El zip resultante pesa menos que el límite\n")
		err = enviarMensaje(mensaje, token, idChat)
		if err != nil {
			return err
		}
		err = enviarUnArchivo(salida, token, idChat)
		if err != nil {
			return err
		}
		return eliminarVariosArchivos([]string{salida})
	} else {
		ubicaciones, err := separarArchivoEnVariasPartes(salida, MaximoTamañoArchivoTelegram)
		if err != nil {
			return err
		}
		cantidadUbicaciones := len(ubicaciones)
		mensaje += fmt.Sprintf("Separando archivo en %d partes\n", cantidadUbicaciones)
		err = enviarMensaje(mensaje, token, idChat)
		if err != nil {
			return err
		}
		for _, ubicacion := range ubicaciones {

			err = enviarUnArchivo(ubicacion, token, idChat)
			if err != nil {
				return err
			}
		}

		ubicaciones = append(ubicaciones, salida)
		return eliminarVariosArchivos(ubicaciones)
	}
}

Poniendo todo junto

Llegamos a la función main del programa que lee los argumentos pasados a través de la línea de comandos e invoca a la función para hacer la copia de seguridad de un archivo con Telegram:

func main() {
	archivoRespaldar := flag.String("archivo", "", "El archivo o directorio a respaldar")
	tokenTelegram := flag.String("token", "", "Tu token de Telegram")
	idChat := flag.String("id_chat", "", "El ID del chat al que quieres enviar el archivo")
	flag.Parse()
	if *archivoRespaldar == "" || *tokenTelegram == "" || *idChat == "" {
		flag.PrintDefaults()
		return
	}
	err := respaldar(*archivoRespaldar, *tokenTelegram, *idChat)
	if err != nil {
		err = enviarMensaje(err.Error(), *tokenTelegram, *idChat)
		if err != nil {
			fmt.Printf("Error: %v", err)
		}
	}
}

El código fuente completo te lo dejaré en GitHub. Una vez que lo hayas descargado ejecuta go build para generar el ejecutable (en este caso se llama respaldar_archivo_telegram.exe). Luego ejecuta así:

respaldar_archivo_telegram.exe -archivo "El archivo o directorio" -token "Tu token de Telegram" -id_chat "El id del usuario, canal o grupo"

En el mismo repositorio de GitHub vas a encontrar el ejecutable en el apartado “Releases“, así, aunque no tengas conocimientos de programación puedes ejecutar el Bot para respaldar archivos en Telegram, sin necesidad de compilarlo por ti mismo.

Actualización

He agregado la función de Cron a este código para respaldar un archivo o directorio periódicamente:

Respaldo periódico con Bot de Telegram y Cron

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *