Last active
July 13, 2025 07:34
-
-
Save weskerty/a5c275b2b6684b0d07cdc896a680d002 to your computer and use it in GitHub Desktop.
Descarga TG a Wa
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs').promises; | |
const { createReadStream, existsSync, statSync } = require('fs'); | |
const path = require('path'); | |
const { exec } = require('child_process'); | |
const { promisify } = require('util'); | |
const { bot } = require('../lib'); | |
require('dotenv').config(); | |
const execPromise = promisify(exec); | |
const FILE_TYPES = { | |
video: { | |
extensions: new Set(['mp4', 'mkv', 'avi', 'webm', 'mov', 'flv', 'm4v']), | |
mimetype: 'video/mp4', | |
}, | |
image: { | |
extensions: new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'svg']), | |
mimetype: 'image/jpeg', | |
}, | |
document: { | |
extensions: new Set(['pdf', 'epub', 'docx', 'txt', 'apk', 'apks', 'zip', 'rar', 'iso', 'ini', 'cbr', 'cbz', 'torrent', 'json', 'xml', 'html', 'css', 'js', 'csv', 'xls', 'xlsx', 'ppt', 'pptx']), | |
mimetypes: new Map([ | |
['pdf', 'application/pdf'], | |
['epub', 'application/epub+zip'], | |
['docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], | |
['txt', 'text/plain'], | |
['apk', 'application/vnd.android.package-archive'], | |
['apks', 'application/vnd.android.package-archive'], | |
['zip', 'application/zip'], | |
['rar', 'application/x-rar-compressed'], | |
['iso', 'application/x-iso9660-image'], | |
['ini', 'text/plain'], | |
['cbr', 'application/x-cbr'], | |
['cbz', 'application/x-cbz'], | |
['torrent', 'application/x-bittorrent'], | |
['json', 'application/json'], | |
['xml', 'application/xml'], | |
['html', 'text/html'], | |
['css', 'text/css'], | |
['js', 'application/javascript'], | |
['csv', 'text/csv'], | |
['xls', 'application/vnd.ms-excel'], | |
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], | |
['ppt', 'application/vnd.ms-powerpoint'], | |
['pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], | |
]), | |
defaultMimetype: 'application/octet-stream', | |
}, | |
audio: { | |
extensions: new Set(['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'wma']), | |
mimetype: 'audio/mpeg', | |
}, | |
}; | |
function getFileDetails(filePath) { | |
const ext = path.extname(filePath).slice(1).toLowerCase(); | |
for (const [category, typeInfo] of Object.entries(FILE_TYPES)) { | |
if (typeInfo.extensions.has(ext)) { | |
return { | |
category, | |
mimetype: category === 'document' | |
? typeInfo.mimetypes.get(ext) || typeInfo.defaultMimetype | |
: typeInfo.mimetype, | |
}; | |
} | |
} | |
return { | |
category: 'document', | |
mimetype: FILE_TYPES.document.defaultMimetype, | |
}; | |
} | |
function formatFileSize(bytes) { | |
if (bytes < 1024) return bytes + ' B'; | |
if (bytes < 1048576) return (bytes / 1024).toFixed(2) + ' KB'; | |
if (bytes < 1073741824) return (bytes / 1048576).toFixed(2) + ' MB'; | |
return (bytes / 1073741824).toFixed(2) + ' GB'; | |
} | |
async function safeExecute(command, silentError = false) { | |
try { | |
return await execPromise(command); | |
} catch (error) { | |
if (!silentError) { | |
console.error(`TDL Error: ${error.message}`); | |
} | |
throw new Error('Ejecución fallida'); | |
} | |
} | |
class RequestQueue { | |
constructor(maxConcurrent = 1) { | |
this.queue = []; | |
this.activeRequests = 0; | |
this.maxConcurrent = maxConcurrent; | |
} | |
async addRequest(type, task, message) { | |
return new Promise((resolve, reject) => { | |
this.queue.push({ type, task, message, resolve, reject }); | |
this.processNext(); | |
}); | |
} | |
async processNext() { | |
if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) { | |
return; | |
} | |
this.activeRequests++; | |
const { type, task, resolve, reject } = this.queue.shift(); | |
try { | |
const result = await task(); | |
resolve(result); | |
} catch (error) { | |
reject(error); | |
} finally { | |
this.activeRequests--; | |
this.processNext(); | |
} | |
} | |
} | |
class TDLDownloader { | |
constructor() { | |
this.config = { | |
jsonDir: path.join(process.cwd(), 'tmp', 'jsons'), | |
downloadDir: process.env.AMULEDOWNLOADS || path.join(process.cwd(), 'tmp', 'downloads'), | |
tdlPath: path.join(process.cwd(), 'media', 'bin', 'tdl'), | |
defaultChatIds: (process.env.CHATSTELEGRAMID || '1143692078').split(',').map(id => id.trim()), | |
maxFileSize: (parseInt(process.env.MAX_UPLOAD, 10) * 1048576) || 1500000000, | |
shouldDeleteTempFiles: process.env.DELETE_TEMP_FILE !== 'false', | |
maxConcurrent: parseInt(process.env.MAXSOLICITUD, 10) || 1 | |
}; | |
this.requestQueue = new RequestQueue(this.config.maxConcurrent); | |
this.systemTdl = null; | |
this.searchResults = new Map(); | |
this.fileNameMap = new Map(); | |
this.currentDownloadFiles = new Set(); | |
this.currentSessionDir = null; | |
} | |
async ensureDirectories() { | |
await Promise.all([ | |
fs.mkdir(this.config.jsonDir, { recursive: true }), | |
fs.mkdir(this.config.downloadDir, { recursive: true }), | |
fs.mkdir(path.dirname(this.config.tdlPath), { recursive: true }) | |
]); | |
} | |
async findSystemTdl() { | |
try { | |
await execPromise('tdl version'); | |
this.systemTdl = 'tdl'; | |
return true; | |
} catch { | |
return false; | |
} | |
} | |
async getTdlPath() { | |
if (!this.systemTdl && await this.findSystemTdl()) { | |
return this.systemTdl; | |
} | |
if (this.systemTdl) return this.systemTdl; | |
try { | |
await fs.access(this.config.tdlPath); | |
return this.config.tdlPath; | |
} catch { | |
return this.downloadTdl(); | |
} | |
} | |
async downloadTdl() { | |
const downloadUrl = 'https://github.com/iyear/tdl/releases/latest/download/tdl_Linux_64bit.tar.gz'; | |
const tempDir = path.dirname(this.config.tdlPath); | |
const tempPath = path.join(tempDir, 'tdl_Linux_64bit.tar.gz'); | |
try { | |
await safeExecute(`curl -L -o "${tempPath}" "${downloadUrl}"`); | |
} catch { | |
try { | |
const fetch = (await import('node-fetch')).default; | |
const response = await fetch(downloadUrl); | |
if (!response.ok) throw new Error(`Descarga fallida: ${response.statusText}`); | |
const buffer = Buffer.from(await response.arrayBuffer()); | |
await fs.writeFile(tempPath, buffer); | |
} catch (error) { | |
throw new Error(`Error al descargar TDL: ${error.message}`); | |
} | |
} | |
await safeExecute(`tar -xzf "${tempPath}" -C "${tempDir}"`); | |
await fs.chmod(this.config.tdlPath, '755'); | |
await fs.unlink(tempPath).catch(() => {}); | |
return this.config.tdlPath; | |
} | |
createSessionDir() { | |
const sessionId = `tdl_${Date.now()}`; | |
this.currentSessionDir = path.join(this.config.downloadDir, 'tdl', sessionId); | |
return this.currentSessionDir; | |
} | |
async cleanupFiles() { | |
if (!this.config.shouldDeleteTempFiles) return; | |
try { | |
if (this.currentSessionDir) { | |
await fs.rm(this.currentSessionDir, { recursive: true, force: true }).catch(() => {}); | |
} | |
this.currentDownloadFiles.clear(); | |
this.currentSessionDir = null; | |
} catch (error) { | |
console.error('Error al limpiar archivos:', error); | |
} | |
} | |
async cleanAllFiles() { | |
if (!this.config.shouldDeleteTempFiles) return; | |
try { | |
const jsonFiles = await fs.readdir(this.config.jsonDir); | |
for (const file of jsonFiles) { | |
if (file.startsWith('selected_') || file.startsWith('temp_')) { | |
await fs.unlink(path.join(this.config.jsonDir, file)).catch(() => {}); | |
} | |
} | |
} catch (error) { | |
console.error('Error al limpiar archivos:', error); | |
} | |
} | |
async getAllChatIds() { | |
const tdlPath = await this.getTdlPath(); | |
try { | |
const output = await execPromise(`${tdlPath} chat ls`); | |
const lines = output.trim().split('\n').slice(1); | |
const chatIds = []; | |
for (const line of lines) { | |
const parts = line.trim().split(/\s+/); | |
if (parts.length >= 2) { | |
const chatId = parts[0].trim(); | |
if (/^\d+$/.test(chatId) && !chatId.includes(':')) { | |
chatIds.push(chatId); | |
} | |
} | |
} | |
return chatIds; | |
} catch (error) { | |
console.error('Error al obtener IDs de chats:', error); | |
return this.config.defaultChatIds; | |
} | |
} | |
isFileSizeAllowed(filePath) { | |
try { | |
const stats = statSync(filePath); | |
return { | |
allowed: stats.size <= this.config.maxFileSize, | |
size: stats.size, | |
formattedSize: formatFileSize(stats.size), | |
maxSize: formatFileSize(this.config.maxFileSize) | |
}; | |
} catch (error) { | |
return { allowed: false, error: error.message }; | |
} | |
} | |
async downloadTelegramLinks(message, urls) { | |
return this.requestQueue.addRequest('download', async () => { | |
const tdlPath = await this.getTdlPath(); | |
const sessionDir = this.createSessionDir(); | |
await this.ensureDirectories(); | |
await fs.mkdir(sessionDir, { recursive: true }); | |
await this.cleanupFiles(); | |
this.currentDownloadFiles.clear(); | |
try { | |
await message.send(`Descargando desde ${urls.length} enlace(s)...`, { quoted: message.data }); | |
let downloadCommand = `${tdlPath} dl --skip-same --template "{{ filenamify .FileName }}" -d ${sessionDir}`; | |
for (const url of urls) { | |
downloadCommand += ` -u "${url}"`; | |
} | |
await execPromise(downloadCommand); | |
await this.processDownloadedFiles(message, sessionDir); | |
} catch (error) { | |
throw new Error(`Error al descargar: ${error.message}`); | |
} finally { | |
if (this.config.shouldDeleteTempFiles) { | |
await this.cleanupFiles(); | |
} | |
} | |
}, message); | |
} | |
async exportAllChats() { | |
const tdlPath = await this.getTdlPath(); | |
const chatIds = await this.getAllChatIds(); | |
await this.ensureDirectories(); | |
const results = []; | |
for (const chatId of chatIds) { | |
const exportFile = path.join(this.config.jsonDir, `export_${chatId}.json`); | |
try { | |
await execPromise(`${tdlPath} chat export -c ${chatId} -o ${exportFile}`); | |
results.push({ chatId, file: exportFile, success: true }); | |
} catch (error) { | |
results.push({ chatId, error: error.message, success: false }); | |
} | |
} | |
return results; | |
} | |
async checkDatabaseAge() { | |
try { | |
const jsonFiles = await fs.readdir(this.config.jsonDir); | |
for (const file of jsonFiles) { | |
if (file.startsWith('export_') && file.endsWith('.json')) { | |
const filePath = path.join(this.config.jsonDir, file); | |
const stats = await fs.stat(filePath); | |
const sixMonthsAgo = new Date(); | |
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); | |
if (stats.mtime < sixMonthsAgo) return true; | |
} | |
} | |
return false; | |
} catch { | |
return false; | |
} | |
} | |
async searchInChats(message, searchQuery) { | |
return this.requestQueue.addRequest('search', async () => { | |
const tdlPath = await this.getTdlPath(); | |
await this.ensureDirectories(); | |
this.searchResults.clear(); | |
this.fileNameMap.clear(); | |
let chatIds = []; | |
let needsExport = false; | |
try { | |
const jsonFiles = await fs.readdir(this.config.jsonDir); | |
const validJsonFiles = jsonFiles.filter(file => file.startsWith('export_') && file.endsWith('.json')); | |
if (validJsonFiles.length === 0) { | |
needsExport = true; | |
chatIds = await this.getAllChatIds(); | |
} else { | |
chatIds = validJsonFiles.map(file => { | |
const match = file.match(/export_(\d+)\.json/); | |
return match ? match[1] : null; | |
}).filter(id => id !== null); | |
} | |
if (needsExport) { | |
await this.exportAllChats(); | |
} | |
const isOld = await this.checkDatabaseAge(); | |
const results = []; | |
let totalResults = 0; | |
let resultIndex = 1; | |
for (const chatId of chatIds) { | |
const jsonFile = path.join(this.config.jsonDir, `export_${chatId}.json`); | |
if (!existsSync(jsonFile)) continue; | |
const jsonData = JSON.parse(await fs.readFile(jsonFile, 'utf8')); | |
const chatResults = []; | |
if (jsonData?.messages && Array.isArray(jsonData.messages)) { | |
const searchQueryLower = searchQuery.toLowerCase(); | |
const searchTerms = searchQueryLower.split(/\s+/).filter(term => term.length > 0); | |
for (const msg of jsonData.messages) { | |
if ((msg.file_name || msg.file) && (typeof msg.file_name === 'string' || typeof msg.file === 'string')) { | |
const fileName = msg.file_name || msg.file; | |
const fileNameLower = fileName.toLowerCase(); | |
const matchesAllTerms = searchTerms.every(term => { | |
const termWithSpaces = term.replace(/_/g, ' '); | |
return fileNameLower.includes(term) || fileNameLower.includes(termWithSpaces); | |
}); | |
if (matchesAllTerms) { | |
const fileMetadata = { | |
from: msg.from || null, | |
size: msg.file_size ? formatFileSize(msg.file_size) : null | |
}; | |
chatResults.push({ | |
id: msg.id, | |
file: fileName, | |
resultIndex: resultIndex++, | |
metadata: fileMetadata | |
}); | |
this.searchResults.set(resultIndex - 1, { | |
chatId, | |
messageId: msg.id, | |
file: fileName, | |
jsonFile, | |
metadata: fileMetadata | |
}); | |
this.fileNameMap.set(`${msg.id}_${this.sanitizeFileName(fileName)}`, fileName); | |
totalResults++; | |
} | |
} | |
} | |
} | |
if (chatResults.length > 0) { | |
results.push({ | |
chatId, | |
name: jsonData.name || `Chat ${chatId}`, | |
results: chatResults | |
}); | |
} | |
} | |
if (totalResults === 0) { | |
const msg = isOld ? | |
`Base de datos antigua\nNo se encontraron resultados para "${searchQuery}"` : | |
`No se encontraron resultados para "${searchQuery}"`; | |
await message.send(msg, { quoted: message.data }); | |
return; | |
} | |
let resultMessage = isOld ? "Base de datos antigua\n" : ""; | |
resultMessage += `${totalResults} ℛ𝑒𝓈𝓊𝓁𝓉𝒶𝒹𝑜𝓈\n> _Utiliza tdl dd 1,2,etc para seleccionar_\n`; | |
for (const chatResult of results) { | |
for (const result of chatResult.results) { | |
resultMessage += `\`${result.resultIndex}\` ${result.file}\n`; | |
if (result.metadata) { | |
const metadataInfo = []; | |
if (result.metadata.from) metadataInfo.push(`*De:* _${result.metadata.from}_`); | |
if (result.metadata.size) metadataInfo.push(`*Peso:* _${result.metadata.size}_`); | |
if (metadataInfo.length > 0) { | |
resultMessage += `> ${metadataInfo.join(', ')}\n`; | |
} | |
} | |
resultMessage += '\n'; | |
} | |
} | |
await message.send(resultMessage, { quoted: message.data }); | |
} catch (error) { | |
throw new Error(`Error de búsqueda: ${error.message}`); | |
} | |
}, message); | |
} | |
sanitizeFileName(fileName) { | |
return fileName.replace(/[<>:"/\\|?*]/g, '_'); | |
} | |
async createSelectedFilesJson(indices) { | |
const selectedItems = new Map(); | |
for (const index of indices) { | |
const item = this.searchResults.get(parseInt(index)); | |
if (item) { | |
if (!selectedItems.has(item.chatId)) { | |
selectedItems.set(item.chatId, []); | |
} | |
selectedItems.get(item.chatId).push({ | |
id: item.messageId, | |
type: "message", | |
file: item.file | |
}); | |
} | |
} | |
const jsonFiles = []; | |
for (const [chatId, messages] of selectedItems.entries()) { | |
const jsonContent = { | |
id: parseInt(chatId), | |
messages: messages | |
}; | |
const jsonFilePath = path.join(this.config.jsonDir, `selected_${chatId}_${Date.now()}.json`); | |
await fs.writeFile(jsonFilePath, JSON.stringify(jsonContent)); | |
jsonFiles.push(jsonFilePath); | |
} | |
return jsonFiles; | |
} | |
async downloadSelectedItems(message, indices) { | |
return this.requestQueue.addRequest('download', async () => { | |
const tdlPath = await this.getTdlPath(); | |
const sessionDir = this.createSessionDir(); | |
await this.ensureDirectories(); | |
await fs.mkdir(sessionDir, { recursive: true }); | |
await this.cleanupFiles(); | |
this.currentDownloadFiles.clear(); | |
try { | |
if (this.searchResults.size === 0) { | |
throw new Error('No hay resultados de búsqueda disponibles.'); | |
} | |
const selectedIndices = indices | |
.map(idx => parseInt(idx.trim())) | |
.filter(idx => !isNaN(idx) && this.searchResults.has(idx)); | |
if (selectedIndices.length === 0) { | |
throw new Error('No se seleccionaron elementos válidos.'); | |
} | |
const jsonFiles = await this.createSelectedFilesJson(selectedIndices); | |
if (jsonFiles.length === 0) { | |
throw new Error('Error al crear archivos de selección.'); | |
} | |
await message.send(`Descargando ${selectedIndices.length} archivos...`, { quoted: message.data }); | |
let downloadCommand = `${tdlPath} dl --skip-same --template "{{ filenamify .FileName }}" -d ${sessionDir}`; | |
for (const jsonFile of jsonFiles) { | |
downloadCommand += ` -f "${jsonFile}"`; | |
} | |
await execPromise(downloadCommand); | |
await this.processDownloadedFiles(message, sessionDir); | |
} catch (error) { | |
throw new Error(`Error de descarga: ${error.message}`); | |
} finally { | |
await this.cleanAllFiles(); | |
if (this.config.shouldDeleteTempFiles) { | |
await this.cleanupFiles(); | |
} | |
} | |
}, message); | |
} | |
async processDownloadedFiles(message, sessionDir) { | |
try { | |
const files = await fs.readdir(sessionDir); | |
const downloadedFiles = files.filter(file => !file.endsWith('.json') && !file.includes('tdl')); | |
if (downloadedFiles.length === 0) { | |
await message.send("❌ No se encontraron archivos descargados", { quoted: message.data }); | |
return; | |
} | |
const skippedFiles = []; | |
const processableFiles = []; | |
for (const file of downloadedFiles) { | |
const filePath = path.join(sessionDir, file); | |
const stats = await fs.stat(filePath); | |
if (stats.isDirectory()) { | |
continue; | |
} | |
const sizeCheck = this.isFileSizeAllowed(filePath); | |
if (sizeCheck.allowed) { | |
processableFiles.push(filePath); | |
} else { | |
skippedFiles.push({ | |
name: file, | |
size: sizeCheck.formattedSize | |
}); | |
} | |
} | |
if (processableFiles.length > 0) { | |
for (const file of processableFiles) { | |
try { | |
const { category, mimetype } = getFileDetails(file); | |
const stream = createReadStream(file); | |
const basename = path.basename(file); | |
const match = basename.match(/^(\d+)_(.+)$/); | |
let fileName = basename; | |
if (match && match.length >= 3) { | |
const messageId = parseInt(match[1]); | |
for (const [key, originalName] of this.fileNameMap.entries()) { | |
if (key.startsWith(`${messageId}_`)) { | |
fileName = originalName; | |
break; | |
} | |
} | |
} | |
await message.send(stream, { | |
fileName, | |
mimetype, | |
quoted: message.data | |
}, category); | |
} catch (error) { | |
await message.send(`❌ Error al enviar ${path.basename(file)}: ${error.message}`, { quoted: message.data }); | |
} | |
} | |
} | |
if (skippedFiles.length > 0) { | |
if (processableFiles.length === 0) { | |
let errorMsg = "❌ Todos los archivos exceden el límite\n"; | |
const filesToShow = skippedFiles.slice(0, 5); | |
errorMsg += filesToShow.map(f => `- ${f.name} (${f.size})`).join("\n"); | |
if (skippedFiles.length > 5) { | |
errorMsg += `\n...y ${skippedFiles.length - 5} más`; | |
} | |
errorMsg += `\nTamaño máximo: ${this.config.maxFileSize / 1048576} MB`; | |
await message.send(errorMsg, { quoted: message.data }); | |
} else { | |
const sizeExceededNames = skippedFiles.map(f => f.name).slice(0, 3); | |
let summaryMsg = `⚠️ ${skippedFiles.length} archivo(s) excedieron el límite`; | |
if (sizeExceededNames.length > 0) { | |
summaryMsg += `: ${sizeExceededNames.join(', ')}${skippedFiles.length > 3 ? '...' : ''}`; | |
} | |
await message.send(summaryMsg, { quoted: message.data }); | |
} | |
} | |
} catch (error) { | |
await message.send(`❌ Error al procesar descargas: ${error.message}`, { quoted: message.data }); | |
} | |
} | |
} | |
const tdlDownloader = new TDLDownloader(); | |
bot( | |
{ | |
pattern: 'tdl ?(.*)', | |
fromMe: true, | |
desc: 'Buscar y descargar archivos de chats de Telegram usando TDL.', | |
type: 'download', | |
}, | |
async (message, match) => { | |
const input = match.trim() || message.reply_message?.text || ''; | |
if (!input) { | |
await message.send( | |
'> 🔍 Buscar: `tdl` <búsqueda>\n' + | |
'> 📥 Descargar: `tdl dd` <índices>\n' + | |
'> 📁 Enlace: `tdl` <https://t.me/...>\n' + | |
'> 💾 Exportar: `tdl export`\n' + | |
'> 🆙 Actualizar: `tdl update`', | |
{ quoted: message.data } | |
); | |
return; | |
} | |
try { | |
const args = input.split(' '); | |
if (args[0].toLowerCase() === 'update') { | |
const tdlPath = await tdlDownloader.downloadTdl(); | |
await message.send(`✅ TDL actualizado: ${tdlPath}`, { quoted: message.data }); | |
return; | |
} | |
if (args[0].toLowerCase() === 'export') { | |
await message.send(`💾 Exportando chats...`, { quoted: message.data }); | |
const results = await tdlDownloader.exportAllChats(); | |
const successCount = results.filter(r => r.success).length; | |
await message.send(`✅ Exportados ${successCount}/${results.length} chats`, { quoted: message.data }); | |
return; | |
} | |
if (args[0].toLowerCase() === 'dd') { | |
const indices = args.slice(1).join('').split(/[,\s]+/); | |
await tdlDownloader.downloadSelectedItems(message, indices); | |
return; | |
} | |
const telegramLinks = args.filter(arg => /^https?:\/\/(t\.me|telegram\.me)\//i.test(arg)); | |
if (telegramLinks.length > 0) { | |
await tdlDownloader.downloadTelegramLinks(message, telegramLinks); | |
return; | |
} | |
await tdlDownloader.searchInChats(message, input); | |
} catch (error) { | |
await message.send(`❌ ${error.message}`, { quoted: message.data }); | |
} | |
} | |
); | |
module.exports = { tdlDownloader }; |
❌ ENOENT: no such file or directory
Try Now.
It works for me, gg
You must manually log in to tdl from the server, the script does not have the ability to do this.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
❌ ENOENT: no such file or directory, open '/home/container/levanter/media/bin/tdl_Linux_64bit.tar.gz'
Please fix this problem