Last active
July 22, 2025 14:05
-
-
Save weskerty/5311f55462e5b84736bde5b1ac7e2ae3 to your computer and use it in GitHub Desktop.
Telegram Sticker Downloader
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
// dtg.js is a complement to LevanterBot: https://github.com/lyfe00011/levanter | |
// github.com/weskerty/TGStickerDownloader | |
// Copyright (C) 2025 Weskerty | |
// | |
// Este programa se distribuye bajo los términos de la Licencia Pública General Affero de GNU (AGPLv3). | |
// Usted puede usarlo, modificarlo y redistribuirlo bajo esa licencia. | |
// Este software se proporciona SIN GARANTÍA alguna. | |
// Licencia completa: https://www.gnu.org/licenses/agpl-3.0.html | |
const fs = require('fs').promises; | |
const path = require('path'); | |
const os = require('os'); | |
const { promisify } = require('util'); | |
const { exec: execCallback } = require('child_process'); | |
const { bot, isUrl, sticker, addExif } = require('../lib'); | |
require('dotenv').config(); | |
const exec = promisify(execCallback); | |
class DownloadQueue { | |
constructor(maxConcurrent = 2) { | |
this.queue = []; | |
this.activeDownloads = 0; | |
this.maxConcurrent = maxConcurrent; | |
} | |
async add(task) { | |
return new Promise((resolve, reject) => { | |
this.queue.push({ task, resolve, reject }); | |
this.processNext(); | |
}); | |
} | |
async processNext() { | |
if (this.activeDownloads >= this.maxConcurrent || this.queue.length === 0) { | |
return; | |
} | |
this.activeDownloads++; | |
const { task, resolve, reject } = this.queue.shift(); | |
try { | |
const result = await task(); | |
resolve(result); | |
} catch (error) { | |
reject(error); | |
} finally { | |
this.activeDownloads--; | |
this.processNext(); | |
} | |
} | |
} | |
class StickerDownloader { | |
constructor() { | |
this.config = { | |
apiKey: process.env.TGBOTAPIKEY || '', | |
converterPath: path.join(process.cwd(), 'media', 'bin'), | |
tgsdPath: path.join(process.cwd(), 'media', 'bin', 'TGSD'), | |
maxConcurrent: parseInt(process.env.MAXSOLICITUD, 10) || 1, | |
}; | |
this.downloadQueue = new DownloadQueue(this.config.maxConcurrent); | |
this.converterBinaries = new Map([ | |
['win32-x64', 'lottie-converter.windows.amd64.exe.zip'], | |
['linux-x64', 'lottie-converter.linux.amd64.zip'], | |
['linux-arm64', 'lottie-converter.linux.arm64.zip'], | |
['default', 'lottie-converter.linux.amd64.zip'], | |
]); | |
} | |
async safeExecute(command, silentError = false) { | |
try { | |
const result = await exec(command); | |
return result; | |
} catch (error) { | |
if (!silentError) { | |
console.error(`Execution failed: ${error.message}`); | |
} | |
throw new Error('Execution failed'); | |
} | |
} | |
detectConverterBinaryName() { | |
const platform = os.platform(); | |
const arch = os.arch(); | |
const key = `${platform}-${arch}`; | |
return this.converterBinaries.get(key) || this.converterBinaries.get('default'); | |
} | |
async ensureDirectories() { | |
await Promise.all([ | |
fs.mkdir(this.config.converterPath, { recursive: true }), | |
]); | |
} | |
async isScriptAvailable() { | |
const scriptPath = path.join(this.config.tgsdPath, 'DownloaderFlags.sh'); | |
try { | |
await fs.access(scriptPath); | |
return true; | |
} catch { | |
return false; | |
} | |
} | |
async downloadConverterBundle() { | |
await this.ensureDirectories(); | |
const fileName = this.detectConverterBinaryName(); | |
const platform = os.platform(); | |
const key = `${platform}-${os.arch()}`; | |
let downloadUrl; | |
if (key === 'win32-x64') { | |
downloadUrl = 'https://github.com/weskerty/TGStickerDownloader/releases/download/1.1.2/lottie-converter.windows.amd64.exe.zip'; | |
} else if (key === 'linux-x64') { | |
downloadUrl = 'https://github.com/weskerty/TGStickerDownloader/releases/download/1.1.2/lottie-converter.linux.amd64.zip'; | |
} else if (key === 'linux-arm64' || platform === 'android') { | |
downloadUrl = 'https://github.com/weskerty/TGStickerDownloader/releases/download/1.1.2/lottie-converter.linux.arm64.zip'; | |
} else { | |
downloadUrl = 'https://github.com/weskerty/TGStickerDownloader/releases/download/1.1.2/lottie-converter.linux.amd64.zip'; | |
} | |
const filePath = path.join(this.config.converterPath, fileName); | |
const fetch = (await import('node-fetch')).default; | |
const response = await fetch(downloadUrl); | |
if (!response.ok) throw new Error(`Download failed: ${response.statusText}`); | |
const buffer = Buffer.from(await response.arrayBuffer()); | |
await fs.writeFile(filePath, buffer); | |
await this.safeExecute(`unzip -o "${filePath}" -d "${this.config.converterPath}"`); | |
if (platform !== 'win32') { | |
await this.safeExecute(`chmod -R 755 "${this.config.tgsdPath}"`).catch(() => {}); | |
} | |
} | |
async downloadStickerPack(message, packName) { | |
return this.downloadQueue.add(async () => { | |
try { | |
await this.ensureDirectories(); | |
if (!(await this.isScriptAvailable())) { | |
await this.downloadConverterBundle(); | |
} | |
let processedPackName = packName; | |
if (packName.startsWith('https://t.me/addstickers/')) { | |
processedPackName = packName.replace('https://t.me/addstickers/', ''); | |
} | |
const scriptPath = path.join(this.config.tgsdPath, 'DownloaderFlags.sh'); | |
try { | |
await this.safeExecute( | |
`cd "${this.config.tgsdPath}" && bash "${scriptPath}" --key "${this.config.apiKey}" --s "${processedPackName}"` | |
); | |
} catch (execError) { | |
console.error(`Script execution error: ${execError.message}`); | |
await message.send(`Error executing download script.`, { quoted: message.data }); | |
await this.downloadConverterBundle(); | |
await this.safeExecute( | |
`cd "${this.config.tgsdPath}" && bash "${scriptPath}" --key "${this.config.apiKey}" --s "${processedPackName}"` | |
); | |
} | |
const convertedDir = path.join(this.config.tgsdPath, 'converted'); | |
const files = await fs.readdir(convertedDir); | |
if (files.length === 0) { | |
await message.send('No stickers found or conversion failed. NEED `webp` and `jq` READ github.com/weskerty/TGStickerDownloader?tab=readme-ov-file#need-webp-jq', { quoted: message.data }); | |
return; | |
} | |
await message.send(`Sending ${files.length} stickers!...`, { quoted: message.data }); | |
for (const file of files) { | |
try { | |
const filePath = path.join(convertedDir, file); | |
const fileData = await fs.readFile(filePath); | |
await message.send(fileData, { isAnimated: true }, 'sticker'); | |
} catch (error) { | |
console.error(`Error sending sticker ${file}: ${error.message}`); | |
} | |
} | |
await fs.rm(path.join(this.config.tgsdPath, 'TGSs'), { recursive: true, force: true }).catch(() => {}); | |
await fs.rm(convertedDir, { recursive: true, force: true }).catch(() => {}); | |
await fs.mkdir(path.join(this.config.tgsdPath, 'TGSs'), { recursive: true }); | |
await fs.mkdir(convertedDir, { recursive: true }); | |
} catch (error) { | |
console.error(`Error downloading sticker pack: ${error.message}`); | |
await message.send(`Error ${error.message}`, { quoted: message.data }); | |
} | |
}); | |
} | |
} | |
const stickerDownloader = new StickerDownloader(); | |
bot( | |
{ | |
pattern: 'dtg ?(.*)', | |
fromMe: true, | |
desc: 'Telegram Sticker Pack Downloader.', | |
type: 'download', | |
}, | |
async (message, match) => { | |
const input = match.trim() || message.reply_message?.text || ''; | |
if (!input) { | |
await message.send( | |
'> 🎭 Download Telegram Sticker Pack:\n`dtg` <pack_name>\n' + | |
'> 🔗 Or with URL:\n`dtg` <https://t.me/addstickers/pack_name >\n' + | |
'> ℹ️ Create a Bot with t.me/BotFather in Telegram and add the APIKEY with `.setvar TGBOTAPIKEY=` \n' + | |
'> ⚠️ NEED `webp` and `jq` READ github.com/weskerty/TGStickerDownloader?tab=readme-ov-file#need-webp-jq', | |
{ quoted: message.data } | |
); | |
return; | |
} | |
try { | |
if (!stickerDownloader.config.apiKey) { | |
await message.send( | |
'Error: TGBOTAPIKEY not set. \n Create a Bot with @BotFather in Telegram and add the APIKEY with `.setvar TGBOTAPIKEY=`', | |
{ quoted: message.data } | |
); | |
return; | |
} | |
await stickerDownloader.downloadStickerPack(message, input); | |
} catch (error) { | |
console.error(`Error in dtg command: ${error.message}`); | |
await message.send(`Error ${error.message}`, { quoted: message.data }); | |
} | |
} | |
); | |
module.exports = { stickerDownloader }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment