├── .gitignore ├── README.md ├── package.json ├── config.js.example ├── LICENSE ├── lang ├── zh-hant.json ├── zh-hans.json ├── en.json ├── ua.json ├── ru.json ├── fr.json ├── de.json └── pt.json └── bot.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | test 3 | config.js 4 | storage 5 | npm-debug.log 6 | node_modules 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StickerImageBot 2 | =============== 3 | 4 | Bot to export telegram stickers to images. [Here is a sample one to play with (Not sure it's running)](https://telegram.me/stickerset2packbot) 5 | 6 | Send individual stickers or sticker links (something like `https://t.me/addstickers/AniColle`) to prepare a zip of sticker image file. 7 | 8 | ### Requirements 9 | 10 | * Node.js v8.0.0^ 11 | * ImageMagick with webp support (Check with `identify -list format | grep -i 'webp'` on *nix systems) 12 | * [lottieconv](https://crates.io/crates/lottieconv) 13 | 14 | ### Usage 15 | 16 | 1. git clone 17 | 2. Get a bot token from [@BotFather](https://telegram.me/BotFather) 18 | 3. Copy `config.js.example` to `config.js` and edit as your needs 19 | 4. `npm install && npm start` 20 | 21 | ### License 22 | 23 | MIT 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telegram-stickerimage-bot", 3 | "version": "0.0.1", 4 | "description": "Export telegram stickers to images", 5 | "main": "bot.js", 6 | "scripts": { 7 | "start": "node bot.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/phoenixlzx/telegram-stickerimage-bot.git" 12 | }, 13 | "keywords": [ 14 | "telegram", 15 | "bot", 16 | "sticker", 17 | "image" 18 | ], 19 | "author": "Phoenix Nemo ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/phoenixlzx/telegram-stickerimage-bot/issues" 23 | }, 24 | "homepage": "https://github.com/phoenixlzx/telegram-stickerimage-bot#readme", 25 | "dependencies": { 26 | "axios": "^1.7.7", 27 | "fluent-ffmpeg": "^2.1.3", 28 | "fs-extra": "^11.2.0", 29 | "jszip": "^3.10.1", 30 | "sharp": "^0.33.5", 31 | "telegraf": "^4.16.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /config.js.example: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // telegram bot token and username, get from @BotFather 3 | token: '1234567890:YOURTOKENHERE', 4 | username: 'MyAwesomeStickerBot', 5 | 6 | // binary paths 7 | lottie2gif: 'lottie2gif' 8 | 9 | // max images allowed in one pack 10 | maximages: 50, 11 | 12 | // max upload size by telegram 13 | maxfilebytes: 49 * 1024 * 1024, 14 | 15 | // file storage path 16 | file_storage: './storage', 17 | 18 | // recognized sticker sources 19 | sticker_sources: [ 20 | 'https://t.me/addstickers/', 21 | 'https://telegram.me/addstickers/' 22 | ], 23 | // use language 24 | default_lang: 'en', 25 | available_lang: { 26 | 'en': ['English', 'English'], 27 | 'de': ['German', 'Deutsch'], 28 | 'zh-hans': ['简体中文', '中国'], 29 | 'zh-hant': ['正體中文', '中國'], 30 | 'pt': ['Português (Portugal)'], 31 | 'ru': ['Russian', 'Русский'], 32 | 'ua': ['Ukrainian', 'Українська'] 33 | } 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Phoenix Nemo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lang/zh-hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "你好!\n這是一個貼圖包機器人。你可以給我發送一些貼圖包,然後收到這些貼圖的zip壓縮檔。\n\n一些可用的指令:\n\n- /newpack 開始一個新的貼圖包收集佇列\n- /finish 結束收集並獲取zip壓縮包,如果您想獲得png格式的話,添加 'png',預設留空的話就是jpg格式。寬度可以在最後指定(不要超過512,預設為512)。\n\n例如,'/finish 128'將會得到寬度為128像素的jpg圖像,又如'/finish png 160'可得到160像素寬的png圖像。\n\n- /cancel 取消佇列中的任何任務。\n\n- /lang 改變語言\n\n例如,'/lang'展現所有可用的語言,'/lang de'把語言轉換為德語。", 4 | "newpack": "好的,準備好了,現在向我發送貼圖或貼圖包連結吧!(%max% max.)", 5 | "saved": "貼圖儲存了!你目前建立的收集佇列還剩餘有 %remain% 張貼圖可以添加。向我發送更多貼圖來繼續,或者使用 /finish 來得到你的收集佇列。發送任意文字獲取說明資訊。", 6 | "downloading": "[1/4] 下載圖片中...", 7 | "converting": "[2/4] 轉換圖片中...", 8 | "packaging": "[3/4] 打包所有檔...", 9 | "sending": "[4/4] 正在發送zip壓縮包,這可能需要一點時間——休息,休息一下!", 10 | "nosticker": "抱歉,但你的收集佇列裡沒有貼圖。也許是機器人重啟了,或者是您還沒有發送貼圖?常使用 /newpack 指令開始。", 11 | "taskexist": "你目前正有一個收集佇列。請用 /finish 指令先結束掉它。發送任意文字獲取說明資訊。", 12 | "tasklocked": "你目前正有一個執行中的任務,請稍候直到該任務完成。", 13 | "taskfull": "你的收集佇列滿了!請用 /finish 指令來結束該任務以開始下一個收集佇列。發送任意文字獲取說明資訊。", 14 | "taskcancelled": "你的任務已經被取消。", 15 | "notask": "你目前沒有任務在排隊。也許是機器人重啟了?", 16 | "duplicated_sticker": "你向我發過這個貼圖了。", 17 | "error": "哦……發生了一點錯誤", 18 | "errmsg": "啊,發生了一個錯誤... \n\n%errcode%\n%errbody%\n\n請向 https://github.com/phoenixlzx/telegram-stickerimage-bot/issues 報告錯誤 (不過一般情況下這是Telegram方面的鍋)\n\n你可以用 /cancel 取消,然後用 /newpack 重新開始。", 19 | "language_change": "你選擇了正體中文。", 20 | "language_available": "可用的語言: %languages%\n\n用 /lang 加上語言代碼來切換語言。例如: /lang en", 21 | "get_set_info": "獲取貼圖包訊息中,請稍等...", 22 | "set_added_count": "已從貼圖包添加 %sticker_count% 個表情。", 23 | "err_get_filelink": "獲取檔案 %fileId%. 地址時出錯,跳過...", 24 | "invalid_set": "獲取貼圖包 %setName% 訊息時出錯", 25 | "convert_error": "轉換格式時出錯.", 26 | "download_error": "下載圖片時出錯.", 27 | "direct_task_started": "處理貼紙中... \n\n Tip: 嘗試 /newpack 以:\n - 導出多張貼紙\n - 導出整個貼紙包", 28 | "unsupported_sticker_source": "%source% 不是受支持的貼紙來源。支持的貼圖來源請參考 /sources 命令。", 29 | "supported_sticker_sources": "以下鏈接 URL 是機器人支持的貼紙來源:\n\n%sources%\n\n在這些鏈接之一中附加貼紙集標識符,以便機器人可以識別它。" 30 | }, 31 | "app": { 32 | "storagepathnotexist": "儲存路徑不存在,建立中……" 33 | } 34 | } -------------------------------------------------------------------------------- /lang/zh-hans.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "你好!\n这是一个表情包机器人。你可以:\n- 给我发送一些表情,然后收到这些表情的zip压缩文件。\n- 不使用表情包命令而直接发送表情获得 PNG 文件。\n\n一些可用的指令:\n\n- /newpack 开始一个新的表情包收集队列\n- /finish 结束收集并获取zip压缩包,如果您想获得png格式的话,添加 'png',默认留空的话就是jpg格式。宽度可以在最后指定(不要超过512,默认为512)。\n\n例如,'/finish 128'将会得到宽度为128像素的jpg图像,又如'/finish png 160'可得到160像素宽的png图像。\n\n- /cancel 取消队列中的任何任务。\n\n- /lang 改变语言\n\n例如,'/lang'展现所有可用的语言,'/lang de'把语言转换为德语。", 4 | "newpack": "好的,准备好了,现在向我发送表情或表情包链接吧!(最多 %max% 个) ", 5 | "saved": "表情保存啦!你当前建立的收集队列还剩余有 %remain% 张表情可以添加。向我发送更多表情来继续,或者使用 /finish 来得到你的收集队列。发送任意文本获取帮助信息。", 6 | "downloading": "[1/4] 下载图片中...", 7 | "converting": "[2/4] 转换图片中...", 8 | "packaging": "[3/4] 打包所有文件...", 9 | "sending": "[4/4] 正在发送zip压缩包,这可能需要一点时间——休息,休息一下!", 10 | "nosticker": "抱歉,但你的收集队列里没有表情。也许是机器人重启了,或者是您还没有发送表情?常使用 /newpack 命令开始。", 11 | "taskexist": "你当前正有一个收集队列。请用 /finish 指令先结束掉它。发送任意文本获取帮助信息。", 12 | "tasklocked": "你当前正有一个运行中的任务,请稍候直到该任务完成。", 13 | "taskfull": "你的收集队列满了!请用 /finish 命令来结束该任务以开始下一个收集队列。发送任意文本获取帮助信息。", 14 | "taskcancelled": "你的任务已经被取消。", 15 | "notask": "你当前没有任务在排队。也许是机器人重启了?", 16 | "duplicated_sticker": "你向我发过这个表情啦o( ̄▽ ̄)o", 17 | "error": "哦……发生了一点错误orz", 18 | "errmsg": "啊啊, 发生了一个错误... \n\n%errcode%\n%errbody%\n\n请向 https://github.com/phoenixlzx/telegram-stickerimage-bot/issues 报告错误 (不过一般情况下这是Telegram方面的锅)\n\n你可以用 /cancel 取消,然后用 /newpack 重新开始。", 19 | "language_change": "你选择了简体中文。", 20 | "language_available": "可用的语言: %languages%\n\n用 /lang 加上语言代码来切换语言。例如: /lang en", 21 | "get_set_info": "获取表情包信息中,请稍等...", 22 | "set_added_count": "已从表情包添加 %sticker_count% 个表情。", 23 | "err_get_filelink": "获取文件 %fileId% 的地址时出错,跳过...", 24 | "invalid_set": "获取表情包 %setName% 信息时出错。", 25 | "convert_error": "转换格式时出错。", 26 | "download_error": "下载图片时出错。", 27 | "direct_task_started": "处理贴纸中... \n\n Tip: 尝试 /newpack 以:\n - 导出多张贴纸\n - 导出整个贴纸包", 28 | "unsupported_sticker_source": "%source% 不是受支持的贴纸来源。支持的贴图来源请参考 /sources 命令。", 29 | "supported_sticker_sources": "以下链接 URL 是机器人支持的贴纸来源:\n\n%sources%\n\n在这些链接之一中附加贴纸集标识符,以便机器人可以识别它。" 30 | }, 31 | "app": { 32 | "storagepathnotexist": "储存路径不存在,创建中……" 33 | } 34 | } -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Hello!\nThis is StickerImage bot. You can:\n- send me some stickers and get a zip file of images.\n- or send me sticker directly to get PNG image.\n\nAvailable commands:\n\n- /newpack to start a new image pack\n- /finish to get compressed zip file, append 'png' if you want to get png images, blank for jpg. Width can be optionally specified at last (no more than 512, blank for 512).\n\nE.g., '/finish 128' will get jpg images resized at 128px in width, or '/finish png 160' for 160px width png files.\n\n- /cancel to cancel any task in your queue.\n\n- /sources to display the supported sources for sticker set links.\n\n- /lang Change the language.\n\nEg. '/lang' show available languages, '/lang de' change the language to german.", 4 | "newpack": "OK now send me stickers or sticker set links (%max% max).", 5 | "saved": "Sticker file saved. Your current pack have %remain% file slots left. Send me more stickers to queue or use /finish to get the pack. Any other text to see help message.", 6 | "downloading": "[1/4] Downloading images...", 7 | "converting": "[2/4] Converting images...", 8 | "packaging": "[3/4] Zipping all files...", 9 | "sending": "[4/4] Sending the .zip file, may take some time. Check back later!", 10 | "nosticker": "Sorry, but you don't have stickers in queue, maybe the bot restarted, or you just not sent any. Try using /newpack to start.", 11 | "taskexist": "You currently have a packaging task, please complete it first using /finish command. Send any other text to see help message.", 12 | "tasklocked": "You currently have a task running, please wait until the task is completed.", 13 | "taskfull": "Your queue is full, please use /finish to complete the task before starting a new one. Send any other text to see help message.", 14 | "taskcancelled": "Your task bas been cancelled.", 15 | "notask": "You currently do not have a task in queue. Maybe the bot restarted?", 16 | "duplicated_sticker": "You have sent me this sticker already.", 17 | "error": "Ouch! An error just occurred.", 18 | "errmsg": "Ahhh, An error occurred... \n\n%errcode%\n%errbody%\n\nPlease report this error to https://github.com/phoenixlzx/telegram-stickerimage-bot/issues (but in most cases this is due to telegram side)\n\nYou may use /cancel and /newpack to restart.", 19 | "language_change": "You have selected english.", 20 | "language_available": "Available languages: %languages%\n\nUse /lang together with the language code to change the language. Eg. /lang en", 21 | "get_set_info": "Getting Sticker Set information, please wait...", 22 | "set_added_count": "Added %sticker_count% sticker(s) from sticker set(s).", 23 | "err_get_filelink": "Error when trying to get file link for %fileId%. Skipping...", 24 | "invalid_set": "Error when trying to get information for %setName%.", 25 | "convert_error": "Error when converting image.", 26 | "download_error": "Error when downloading image.", 27 | "direct_task_started": "Processing your sticker... \n\n Tip: Use /newpack if you want to:\n - Download multiple stickers at once,\n - Export a full set of stickers", 28 | "unsupported_sticker_source": "%source% is not a supported sticker source. Please refer to /sources command for the supported stickers sources.", 29 | "supported_sticker_sources": "The following link URLs are supported sticker sources of the bot:\n\n%sources%\n\nAppend the sticker set identifier in one of these links, so that the bot can recognize it." 30 | }, 31 | "app": { 32 | "storagepathnotexist": "Storage path not exist, creating." 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lang/ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Привіт!\nЯ — бот StickerImage. Ви можете:\n- надіслати мені стікери та отримати zip-архів з оригіналами.\n- надіслати мені стікер, щоб отримати PNG-зображення.\n\nДоступні команди:\n\n- /newpack, щоб створити новий набір стікерів\n- /finish, щоб отримати ZIP-архів зі стікерами. Додайте 'png', якщо хочете отримати PNG-файли. Стандартне розширення файлів — .jpg. Якщо хочете зображення певного розміру, вкажіть його в кінці (не більше 512, за замовчуванням 512).\n\nНаприклад, '/finish 128' надішле JPG-зображення розміром 128px у ширину. '/finish png 160' надішле PNG-зображення шириною 160px.\n\n- /cancel скасовує будь-яку дію.\n\n- /sources відображає підтримувані джерела наборів стікерів.\n\n- /lang змінює мову бота.\n\nНаприклад, '/lang' покаже список доступних мов, '/lang en' змінить мову на англійську.", 4 | "newpack": "Добре, надішліть мені стікери або посилання на набір стікерів (максимум %max% шт.).", 5 | "saved": "Стікери збережено. Ви можете додати ще %remain% стікерів. Надішліть більше стікерів або введіть /finish, щоб отримати оригінали. Надішліть будь-яке повідомлення для допомоги.", 6 | "downloading": "[1/4] Завантажую файли...", 7 | "converting": "[2/4] Конвертую файли...", 8 | "packaging": "[3/4] Створюю архів...", 9 | "sending": "[4/4] Надсилаю ZIP-архів, це може зайняти певний час. Зачекайте трохи!", 10 | "nosticker": "Вибачте, ви не додали стікери в чергу. Можливо, бот перезавантажився або ви не надіслали мені стікер. Скористайтеся командою /newpack, щоб почати.", 11 | "taskexist": "Ви вже почали створення набору стікерів, будь ласка, завершіть поточний набір командою /finish. Надішліть будь-яке повідомлення для допомоги.", 12 | "tasklocked": "Будь ласка, зачекайте завершення поточної задачі...", 13 | "taskfull": "Ваша черга заповнена. Будь ласка, введіть /finish, щоб завершити завдання, перш ніж створювати нове. Надішліть будь-яке повідомлення для допомоги.", 14 | "taskcancelled": "Ви перервали поточне завдання.", 15 | "notask": "Немає поточного завдання. Ймовірно, бот був перезавантажений.", 16 | "duplicated_sticker": "Ви вже надсилали мені цей стікер.", 17 | "error": "Ой! Сталася несподівана помилка.", 18 | "errmsg": "Ой, помилка... \n\n%errcode%\n%errbody%\n\nБудь ласка, повідомте нам про цю помилку: https://github.com/phoenixlzx/telegram-stickerimage-bot/issues (в більшості випадків помилка на стороні Telegram)\n\nВи можете ввести /cancel або /newpack, щоб почати знову.", 19 | "language_change": "Ви змінили мову на українську.", 20 | "language_available": "Доступні мови: %languages%\n\nВведіть /lang [мова], щоб змінити мову. Наприклад, /lang en", 21 | "get_set_info": "Обробляю інформацію про набір стікерів, зачекайте будь ласка...", 22 | "set_added_count": "Додано %sticker_count% стікерів з набору(-ів) стікерів.", 23 | "err_get_filelink": "Помилка при обробці файлу %fileId%. Пропускаю...", 24 | "invalid_set": "Помилка при обробці набору стікерів %setName%.", 25 | "convert_error": "Помилка конвертації зображення.", 26 | "download_error": "Помилка завантаження зображення.", 27 | "direct_task_started": "Обробляю ваш стікер... \n\n Підказка: введіть /newpack, якщо ви хочете:\n - Завантажити кілька стікерів одночасно,\n - Завантажити відразу весь набір стікерів.", 28 | "unsupported_sticker_source": "%source% не підтримується. Будь ласка, введіть /sources, щоб побачити, які посилання на набори стікерів підтримуються.", 29 | "supported_sticker_sources": "Ці посилання підтримуються ботом для завантаження:\n\n%sources%\n\nВикористовуйте набори стікерів з цими посиланнями, щоб бот міг їх обробити." 30 | }, 31 | "app": { 32 | "storagepathnotexist": "Сховище не знайдено. Створюю..." 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lang/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Привет!\nЯ - StickerImage бот. Вы можете:\n- отправить мне стикеры и получить zip архив с исходниками.\n- отправить мне стикер чтобы получить png изображение.\n\nДоступные команды:\n\n- /newpack чтобы создать новый пак\n- /finish чтобы получить .zip архив со стикерами, добавьте 'png' если хотите получить .png изображения. Стандартное расширение файлов - .jpg. Если вы хотите получить изображения с определенным размером, укажите его в конце (не более 512, стандартно 512).\n\nНапример, '/finish 128' отправит jpg изображения размером 128px в ширину. '/finish png 160' отправит png изображения шириной в 160px.\n\n- /cancel отменяет любое действие.\n\n- /sources отображает поддерживаемые источники стикерпаков.\n\n- /lang изменяет язык бота.\n\nНапример, '/lang' покажет список доступных языков, '/lang en' изменит язык на английский.", 4 | "newpack": "Oкей, отправьте мне стикеры или ссылку на стикерпак (максимум %max% шт.).", 5 | "saved": "Стикер сохранён. Вы можете загрузить ещё %remain% стикеров. Отправьте больше стикеров или введите /finish, чтобы получить исходники. Отправьте любое сообщение, чтобы получить помощь.", 6 | "downloading": "[1/4] Скачиваю файлы...", 7 | "converting": "[2/4] Конвертирую файлы...", 8 | "packaging": "[3/4] Собираю архив...", 9 | "sending": "[4/4] Отправляю .zip архив, это может занять время. Возвращайтесь позже!", 10 | "nosticker": "Извините, вы не добавили стикеры в очередь. Возможно, бот был перезагружен или вы не отправили мне стикер. Воспользуйтесь командой /newpack, чтобы начать.", 11 | "taskexist": "Вы уже начали создание пака, пожалуйста завершите текущий пак командой /finish. Отправьте любое сообщение, чтобы получить помощь.", 12 | "tasklocked": "Пожалуйста, дождитесь выполнения текущей задачи...", 13 | "taskfull": "Ваша очередь выполнения заполнена, пожалуйста введите /finish чтобы завершить задачу, прежде чем создавать новую. Отправьте любое сообщение, чтобы получить помощь.", 14 | "taskcancelled": "Вы прервали текущую задачу.", 15 | "notask": "Нет задачи. Скорее всего, бот был перезагружен.", 16 | "duplicated_sticker": "Вы уже отправляли мне этот стикер.", 17 | "error": "Упс! Случилась непредвиденная ошибка.", 18 | "errmsg": "Ой, ошибка... \n\n%errcode%\n%errbody%\n\nПожалуйста, сообщите нам об этой ошибке: https://github.com/phoenixlzx/telegram-stickerimage-bot/issues (в большинстве случаев ошибка на стороне Telegram)\n\nВы можете ввести /cancel или /newpack, чтобы начать заново.", 19 | "language_change": "Вы изменили язык на русский.", 20 | "language_available": "Доступные языки: %languages%\n\nВведите /lang [Язык], чтобы изменить язык. Например, /lang en", 21 | "get_set_info": "Обрабатываю информацию о стикерпаке, пожалуйста подождите...", 22 | "set_added_count": "Добавлено %sticker_count% стикер(ов) из стикерпака (-ов).", 23 | "err_get_filelink": "При обработке файла %fileId% произошла ошибка. Пропускаю...", 24 | "invalid_set": "Ошибка при обработке стикерпака %setName%.", 25 | "convert_error": "Ошибка конвертации изображения.", 26 | "download_error": "Ошибка скачивания изображения.", 27 | "direct_task_started": "Обрабатываю ваш стикер... \n\n Подсказка: введите /newpack если вы хотите:\n - Скачать несколько стикеров сразу,\n - Скачать сразу весь стикерпак.", 28 | "unsupported_sticker_source": "%source% не поддерживается. Пожалуйста, введите /sources чтобы увидеть, какие ссылки на стикерпаки поддерживаются.", 29 | "supported_sticker_sources": "Эти ссылки поддерживаются ботом для скачивания:\n\n%sources%\n\nИспользуйте стикерпаки с этими ссылками, чтобы бот мог их обработать." 30 | }, 31 | "app": { 32 | "storagepathnotexist": "Хранилище не найдено. Создаю..." 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lang/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Bonjour !\nJe suis le bot StickerImage. Vous pouvez m'envoyer des stickers afin de les obtenir sous la forme d'images dans une archive zip.\n\nCommandes disponibles :\n\n- /newpack pour commencer un nouveau pack\n- /finish pour obtenir une archive zip compressée, ajoutez 'png' si vous souhaitez obtenir les images png, laissez vide pour jpg. La taille peut être spécifiée en dernier (pas plus que 512, 512 par défaut soit en laissant vide).\n\nEx : '/finish 128' vous permet d'obtenir les images jpg de taille 128px, ou '/finish png 160' pour les fichiers png de taille 160px.\n\n- /cancel pour annuler toute tâche en cours.\n\n- /lang Change la langue.\n\nEx : '/lang' affiche les langues disponibles, '/lang en' change la langue en français.", 4 | "newpack": "OK, maintenant envoyez-moi des stickers (%max% maximum)", 5 | "saved": "Le sticker a été sauvegardé. Vous pouvez en ajouter encore %remain% dans le pack actuel. Envoyez-moi d'autres stickers pour les ajouter au pack ou tapez /finish pour obtenir le pack. Tout autre message provoquera l'affichage d'une aide.", 6 | "downloading": "[1/4] Téléchargement des images...", 7 | "converting": "[2/4] Conversion des images...", 8 | "packaging": "[3/4] Compression des zip des fichiers...", 9 | "sending": "[4/4] Envoi du fichier .zip, cela peut prendre un certain temps. Revenez plus tard !", 10 | "nosticker": "Désolé, aucun sticker n'est en file d'attente. Peut-être que le bot a redémarré ou que vous n'avez tout simplement pas encore envoyé de sticker. Taper /newpack pour commencer.", 11 | "taskexist": "Un pack est en cours de création, merci de finir cette tâche en tapant /finish. Tout autre message provoquera l'affichage d'une aide.", 12 | "tasklocked": "Une tâche est en cours, merci de patienter jusqu'à ce qu'elle soit terminée.", 13 | "taskfull": "Vous avez atteint la limite maximum, taper /finish pour finir la tâche actuelle avant d'en recommencer une. Tout autre message provoquera l'affichage d'une aide.", 14 | "taskcancelled": "Votre tâche a été annulée.", 15 | "notask": "Vous n'avez aucune tâche en cours. Peut-être que le bot a redémarré.", 16 | "duplicated_sticker": "Vous m'avez déjà envoyé ce sticker.", 17 | "error": "Aïe ! Une erreur est survenue.", 18 | "errmsg": "AH! Une erreur est survenue... \n\n%errcode%\n%errbody%\n\nMerci de rapporter cette erreur à https://github.com/phoenixlzx/telegram-stickerimage-bot/issues (même si dans la plupart des cas cela vient de Telegram)\n\nYou may use /cancel and /newpack to restart.", 19 | "language_change": "Vous avez sélectionné le français.", 20 | "language_available": "Langues disponibles : %languages%\n\nTapez /lang suivi du code de la langue pour changer de langue. Ex : /lang en", 21 | "get_set_info": "Getting Sticker Set information, please wait...", 22 | "set_added_count": "Added %sticker_count% from sticker set(s).", 23 | "err_get_filelink": "Error when trying to get file link for %fileId%. Skipping...", 24 | "invalid_set": "Error when trying to get information for %setName%.", 25 | "convert_error": "Error when converting image.", 26 | "download_error": "Error when downloading image.", 27 | "direct_task_started": "Processing your sticker... \n\n Tip: Use /newpack if you want to:\n - Download multiple stickers at once,\n - Export a full set of stickers", 28 | "unsupported_sticker_source": "%source% n'est pas une source d'autocollant prise en charge. Veuillez vous référer à la commande /sources pour les sources d'autocollants prises en charge.", 29 | "supported_sticker_sources": "Les URL de lien suivantes sont des sources d'autocollants prises en charge par le bot :\n\n%sources%\n\nAjoutez l'identifiant de l'ensemble d'autocollants dans l'un de ces liens, afin que le bot puisse le reconnaître." 30 | }, 31 | "app": { 32 | "storagepathnotexist": "Le chemin d'accès pour le stockage n'existe pas, création." 33 | } 34 | } -------------------------------------------------------------------------------- /lang/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Hallo!\nIch bin der StickerImage-Bot. Du kannst mir Sticker senden, und ich mache dir daraus eine ZIP-Datei mit den Bildern.\n\nVerfügbare Befehle:\n\n- /newpack - Starte ein neues Päckchen.\n- /finish - Lade die ZIP-Datei herunter. Füge 'png' hinzu, wenn du die Bilder als PNG-Dateien (mit transparentem Hintergrund) haben möchtest; andernfalls erhältst du JPGs (mit weißem Hintergrund). Außerdem kannst du die Größe der Bilder festlegen. Die maximale Größe beträgt 512 px.\n\nBeispiele: \"/finish 128\" wird dir die Bilder als JPGs in 128x128 Pixel senden. Mit \"/finish png 160\" bekommst du die Bilder mit transparentem Hintergrund als PNGs, in einer Größe von 160x160 Pixel.\n\n- /cancel - Abbrechen.\n\n- /lang - Die Sprache wechseln.\n\nBeispiel: '/lang' zeigt alle verfügbaren Sprachen an, '/lang en' wechselt die Sprache zu Englisch.", 4 | "newpack": "OK, sende mir nun die Sticker (%max% max.).", 5 | "saved": "Sticker wurde gespeichert. Du kannst noch maximal %remain% Sticker hinzufügen. Verwende /finish, um die Bilder herunterzuladen. Wenn du Hilfe benötigst, sende mir einen beliebigen Text.", 6 | "downloading": "[1/4] Lade Bilder herunter...", 7 | "converting": "[2/4] Konvertiere Bilder...", 8 | "packaging": "[3/4] Verpacke Bilder...", 9 | "sending": "[4/4] Sende ZIP-Datei. Dies kann je nach Anzahl der Bilder einige Zeit dauern. Bitte habe einen Moment Geduld!", 10 | "nosticker": "Leider hast du keine Bilder in der Warteschlange. Entweder hast du mir noch keine Sticker geschickt oder der Bot wurde neu gestartet. Verwende /newpack für ein neues Päckchen.", 11 | "taskexist": "Du arbeitest bereits an einem Päckchen. Schließe es mit /finish ab (für mehr Informationen sende mir einen beliebigen Text).", 12 | "tasklocked": "Eine Aufgabe wird gerade ausgeführt. Bitte warte, bis sie abgeschlossen ist.", 13 | "taskfull": "Du hast die maximale Anzahl an Bildern erreicht. Verwende /finish, um die Bilder herunterzuladen (für mehr Informationen sende mir einen beliebigen Text).", 14 | "taskcancelled": "Das Erstellen des Päckchens wurde abgebrochen.", 15 | "notask": "Momentan wird kein Päckchen erstellt. Möchtest du vielleicht ein Neues starten? Verwende /newpack.", 16 | "duplicated_sticker": "Diesen Sticker hast du mir bereits geschickt.", 17 | "error": "Oops! Ein Fehler ist aufgetreten.", 18 | "errmsg": "Oops! Ein Fehler ist aufgetreten... \n\n%errcode%\n%errbody%\n\nMelde diesen Fehler bitte hier: https://github.com/phoenixlzx/telegram-stickerimage-bot/issues (meistens liegt der Fehler jedoch bei Telegram).\n\nDu kannst mit /cancel abbrechen und mit /newpack neu starten, wenn du möchtest.", 19 | "language_change": "Du hast Deutsch ausgewählt.", 20 | "language_available": "Verfügbare Sprachen: %languages%\n\nVerwende /lang, um eine Sprache auszuwählen. Beispiel: /lang de", 21 | "get_set_info": "Sticker-Set-Informationen werden abgerufen. Bitte warte...", 22 | "set_added_count": "%sticker_count% Sticker aus dem Sticker-Set hinzugefügt.", 23 | "err_get_filelink": "Fehler beim Abrufen des Dateilinks für %fileId%. Überspringe...", 24 | "invalid_set": "Fehler beim Abrufen der Informationen für %setName%.", 25 | "convert_error": "Fehler beim Konvertieren des Bildes.", 26 | "download_error": "Fehler beim Herunterladen des Bildes.", 27 | "direct_task_started": "Sticker wird verarbeitet... \n\n Tipp: Verwende /newpack, wenn du:\n - mehrere Sticker auf einmal herunterladen möchtest,\n - ein vollständiges Sticker-Set exportieren möchtest", 28 | "unsupported_sticker_source": "%source% ist keine unterstützte Stickerquelle. Die unterstützten Stickerquellen findest du im Befehl /sources.", 29 | "supported_sticker_sources": "Die folgenden Link-URLs sind unterstützte Stickerquellen für den Bot:\n\n%sources%\n\nHänge die Sticker-Set-ID an einen dieser Links an, damit der Bot ihn erkennen kann." 30 | }, 31 | "app": { 32 | "storagepathnotexist": "Speicherpfad existiert nicht, wird erstellt." 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lang/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg": { 3 | "start": "Olá!\nEu sou bot e permito-te converter stickers em imagens. Se me enviares:\n- Um link de stickers eu converto em .jpeg, comprimo e envio-te um .zip.\n- Um sticker eu converto diretamente para .jpeg.\n\nComandos disponíveis:\n\n- /newpack inicia a conversão de stickers\n- /finish para terminar a listagem de stickers e receber o .zip. Adicionalmente escreve 'png' se decides receber imagens com fundo transparente. Também podes indicar a dimensão das imagens, usando um numero entre 0 e 512.\n\nExemplos: '/finish 128' dá-te os stickers no formato .jpeg e com a dimensão de 128x128.\n'/finish png 160' converte os stickers no formato .png e dimensão 160x160.\n\n- /cancel permite-te cancelar o processo de conversão.\n\n- /lang permite-te trocar o idioma do bot.\n\nExemplos: '/lang' indica os idiomas disponíveis, '/lang de' altera o idioma do bot para Alemão.", 4 | "newpack": "Iniciado novo processo de conversão. Podes-me enviar stickers ou links de stickers (até %max% stickers).", 5 | "saved": "Sticker armazenado. Podes converter mais %remain% stickers. Envia-me mais stickers ou utiliza o comando /finish para receber o .zip. Qualquer outra mensagem revela a informação de ajuda.", 6 | "downloading": "[1/4] Transferindo os stickers...", 7 | "converting": "[2/4] Convertendo em imagens...", 8 | "packaging": "[3/4] Comprimindo as imagens em .zip...", 9 | "sending": "[4/4] Enviando o ficheiro .zip. O envio pode demorar algum tempo, por isso peço-te que aguardes algum tempo. Irei-te notificar quando o receberes.", 10 | "nosticker": "Desculpa, mas não existem stickers na fila de conversão. No caso de teres enviado algum sticker, é possível que o bot tenha reiniciado. O comando /newpack permite-te começar um novo processo de conversão.", 11 | "taskexist": "De momento já existe um processo de conversão ativo, por favor termina-o com o comando /finish. Qualquer outra mensagem revela a informação de ajuda.", 12 | "tasklocked": "De momento o processo de conversão está em execução, por favor aguarde até que este termine.", 13 | "taskfull": "A fila de conversão está cheia, utiliza o comando /finish para ordenar a conversão e receber o .zip. Qualquer outra mensagem revela a informação de ajuda.", 14 | "taskcancelled": "O processo de conversão foi cancelado.", 15 | "notask": "Não existe nenhum processo de conversão ativo. Se começaste um e não terminaste, é possível que o bot tenha reiniciado.", 16 | "duplicated_sticker": "Já me enviaste este sticker.", 17 | "error": "Oops! Um erro inesperado ocorreu.", 18 | "errmsg": "O seguinte erro ocorreu: \n\n%errcode%\n%errbody%\n\nPor favor reporta o erro abrindo um issue no Github (https://github.com/phoenixlzx/telegram-stickerimage-bot/issues). (É possível que o erro tenha surgido por causa de um problema no Telegram)\n\nPodes utilizar os comandos /cancel e /newpack para reinicar o processo de conversão.", 19 | "language_change": "Selecionaste o idioma Português.", 20 | "language_available": "Idiomas disponíveis: %languages%\n\nEscreve /lang + o código da lingua do idioma para mudar o idioma do bot. Exemplo: /lang en", 21 | "get_set_info": "Obtendo informação do conjunto de stickers, por favor aguarde...", 22 | "set_added_count": "Foram adicionado(s) %sticker_count% sticker(s) do conjunto de stickers.", 23 | "err_get_filelink": "Ocorreu um erro ao obter um link para o ficheiro %fileId%. Skipping...", 24 | "invalid_set": "Ocorreu um erro ao obter informação do conjunto de stickers %setName%.", 25 | "convert_error": "Ocorreu um erro ao converter um sticker em imagem.", 26 | "download_error": "Ocorreu um erro ao transferir um sticker.", 27 | "direct_task_started": "Convertendo o sticker em imagem... \n\n Tip: Utiliza o comando /newpack se desejas:\n - Converter vários stickers simultaneamente,\n - Converter um conjunto completo de stickers." 28 | }, 29 | "app": { 30 | "storagepathnotexist": "Diretório de armazenamento temporário não existe, criando o mesmo." 31 | } 32 | } -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const config = require('./config.js'); 5 | 6 | const fs = require('fs-extra'); 7 | const path = require('path'); 8 | 9 | const { Telegraf } = require('telegraf'); 10 | const sharp = require('sharp'); 11 | const JSZip = require('jszip'); 12 | const axios = require('axios'); 13 | const ffmpeg = require('fluent-ffmpeg'); 14 | 15 | const bot = new Telegraf(config.token); 16 | 17 | let messages = {}; 18 | loadLang(); 19 | 20 | let ramdb = {}; 21 | let langSession = {}; 22 | 23 | bot.use((ctx, next) => { 24 | if (ctx.message && !langSession[ctx.message.chat.id]) { 25 | langSession[ctx.message.chat.id] = config.default_lang; 26 | } 27 | return next(); 28 | }); 29 | 30 | // Check storage path 31 | let fspath = path.resolve(config.file_storage); 32 | if (!fs.existsSync(fspath)) { 33 | logger('INTERNAL', 'info', messages[config.default_lang].app.storagepathnotexist); 34 | fs.mkdirpSync(fspath); 35 | } 36 | 37 | bot.catch((err) => { 38 | logger('INTERNAL', 'error', err); 39 | }); 40 | 41 | bot.command('lang', (ctx) => { 42 | i18nHandler(ctx); 43 | }); 44 | 45 | bot.command('newpack', (ctx) => { 46 | let chatId = ctx.message.chat.id; 47 | if (ramdb[chatId] && ramdb[chatId].islocked) { 48 | return ctx.reply(messages[langSession[chatId]].msg.tasklocked); 49 | } 50 | if (ramdb[chatId] && ramdb[chatId].files.length > 0) { 51 | return ctx.reply(messages[langSession[chatId]].msg.taskexist); 52 | } 53 | newPackHandler(ctx, () => { 54 | return ctx.reply(messages[langSession[chatId]].msg.newpack.replace('%max%', config.maximages)); 55 | }); 56 | }); 57 | 58 | bot.command('finish', async (ctx) => { 59 | let chatId = ctx.message.chat.id; 60 | if (ramdb[chatId] && ramdb[chatId].islocked) { 61 | return ctx.reply(messages[langSession[chatId]].msg.tasklocked); 62 | } 63 | let args = ctx.message.text.split(' ').slice(1).join(' ').trim(); 64 | let format = args === 'png' ? 'png' : 'jpg'; // Default to 'jpg' if no 'png' parameter is provided 65 | if (ramdb[chatId] && ramdb[chatId].files.length > 0) { 66 | await finishHandler(ctx, format); 67 | } else { 68 | ctx.reply(messages[langSession[chatId]].msg.nosticker); 69 | } 70 | }); 71 | 72 | bot.command('sources', (ctx) => { 73 | sourcesHandler(ctx); 74 | }); 75 | 76 | bot.command('cancel', (ctx) => { 77 | cancellationHandler(ctx); 78 | }); 79 | 80 | bot.on('message', (ctx) => { 81 | generalMsgHandler(ctx); 82 | }); 83 | 84 | bot.launch(); 85 | 86 | async function errMsgHandler(ctx, err) { 87 | let chatId = ctx.message.chat.id; 88 | if (err) { 89 | await ctx.reply(messages[langSession[chatId]].msg.errmsg 90 | .replace('%errcode%', err.code || '') 91 | .replace('%errbody%', err.response?.body || err.message)); 92 | } else { 93 | await ctx.reply(messages[langSession[chatId]].msg.error); 94 | } 95 | return cleanup(chatId); 96 | } 97 | 98 | function newPackHandler(ctx, callback) { 99 | let chatId = ctx.message.chat.id; 100 | logger(chatId, 'info', 'Started a new pack task.'); 101 | ramdb[chatId] = { 102 | start: ctx.message.date, 103 | files: [], 104 | srcimg: [], 105 | destimg: [], 106 | islocked: false 107 | }; 108 | callback(); 109 | } 110 | 111 | async function finishHandler(ctx, format) { 112 | try { 113 | let chatId = ctx.message.chat.id; 114 | logger(chatId, 'info', 'Starting pack task...'); 115 | ramdb[chatId].islocked = true; 116 | let fpath = { 117 | packpath: path.join(config.file_storage, chatId.toString()) 118 | }; 119 | fpath['srcpath'] = path.join(fpath.packpath, 'src'); 120 | fpath['imgpath'] = path.join(fpath.packpath, 'img'); 121 | fs.mkdirpSync(fpath.packpath); 122 | fs.mkdirpSync(fpath.srcpath); 123 | fs.mkdirpSync(fpath.imgpath); 124 | 125 | await ctx.reply(messages[langSession[chatId]].msg.downloading); 126 | await downloadHandler(ctx, fpath); 127 | 128 | await ctx.reply(messages[langSession[chatId]].msg.converting); 129 | await convertHandler(ctx, fpath, format); 130 | 131 | await ctx.reply(messages[langSession[chatId]].msg.packaging); 132 | 133 | // Batch images and send ZIP files 134 | await batchAndSendZipFiles(ctx, fpath); 135 | 136 | cleanup(chatId); 137 | logger(chatId, 'info', 'Task finished.'); 138 | } catch (err) { 139 | errMsgHandler(ctx, err); 140 | } 141 | } 142 | 143 | async function batchAndSendZipFiles(ctx, fpath) { 144 | let chatId = ctx.message.chat.id; 145 | let files = ramdb[chatId].destimg; 146 | let batches = []; 147 | let currentBatch = []; 148 | let currentBatchSize = 0; 149 | 150 | // Batch images based on file sizes 151 | for (let file of files) { 152 | let fileSize = fs.statSync(file).size; 153 | if (currentBatchSize + fileSize > config.maxfilebytes && currentBatch.length > 0) { 154 | batches.push(currentBatch); 155 | currentBatch = []; 156 | currentBatchSize = 0; 157 | } 158 | currentBatch.push(file); 159 | currentBatchSize += fileSize; 160 | } 161 | 162 | // Add the last batch if it has any files 163 | if (currentBatch.length > 0) { 164 | batches.push(currentBatch); 165 | } 166 | 167 | logger(chatId, 'info', `Total batches to send: ${batches.length}`); 168 | 169 | // Send each batch as a ZIP file 170 | for (let i = 0; i < batches.length; i++) { 171 | let batch = batches[i]; 172 | let zipFilePath = await createZipForBatch(ctx, fpath, batch, i + 1); 173 | 174 | await ctx.telegram.sendDocument(ctx.from.id, { 175 | source: fs.createReadStream(zipFilePath), 176 | filename: `stickers_${chatId}_${i + 1}.zip` 177 | }); 178 | 179 | // Clean up the sent files and ZIP 180 | fs.unlinkSync(zipFilePath); 181 | for (let file of batch) { 182 | fs.unlinkSync(file); 183 | } 184 | 185 | logger(chatId, 'info', `Sent batch ${i + 1} and cleaned up.`); 186 | } 187 | } 188 | 189 | async function createZipForBatch(ctx, fpath, batchFiles, batchNumber) { 190 | let chatId = ctx.message.chat.id; 191 | let zip = new JSZip(); 192 | 193 | for (let file of batchFiles) { 194 | let fname = path.basename(file); 195 | logger(chatId, 'info', `Adding file ${fname} to batch ${batchNumber}`); 196 | zip.file(fname, fs.readFileSync(file)); 197 | } 198 | 199 | let zipContent = await zip.generateAsync({ 200 | compression: 'DEFLATE', 201 | type: 'nodebuffer', 202 | comment: 'Created by telegram-stickerimage-bot', 203 | platform: process.platform 204 | }); 205 | 206 | let zipFilePath = path.join(fpath.packpath, `stickers_${chatId}_${batchNumber}.zip`); 207 | fs.writeFileSync(zipFilePath, zipContent); 208 | 209 | return zipFilePath; 210 | } 211 | 212 | function cancellationHandler(ctx) { 213 | let chatId = ctx.message.chat.id; 214 | if (!ramdb[chatId]) { 215 | return ctx.reply(messages[langSession[chatId]].msg.notask); 216 | } 217 | delete ramdb[chatId]; 218 | cleanup(chatId); 219 | logger(chatId, 'info', 'Task Cancelled.'); 220 | return ctx.reply(messages[langSession[chatId]].msg.taskcancelled); 221 | } 222 | 223 | function sourcesHandler(ctx) { 224 | let chatId = ctx.message.chat.id; 225 | return ctx.reply(messages[langSession[chatId]].msg.supported_sticker_sources.replace('%sources%', config.sticker_sources.reduce((x, c) => `${x}\n${c}`))); 226 | } 227 | 228 | function generalMsgHandler(ctx) { 229 | let chatId = ctx.message.chat.id; 230 | if (ctx.chat.type !== 'private') return; 231 | if (ramdb[chatId] && !ramdb[chatId].islocked) { 232 | if (ctx.message.sticker) { 233 | addSticker(ctx); 234 | } 235 | if (ctx.message.entities) { 236 | ctx.message.entities.forEach((e) => { 237 | if (e.type === 'url') { 238 | let url = ctx.message.text.slice(e.offset, e.offset + e.length); 239 | if (config.sticker_sources.find((x) => url.startsWith(x)) && 240 | url.length > 25) { 241 | stickerSetHandler(ctx, path.basename(url)); 242 | } else { 243 | ctx.reply(messages[langSession[chatId]].msg.unsupported_sticker_source.replace('%source%', url)); 244 | } 245 | } 246 | }); 247 | } 248 | } else if (!ramdb[chatId] && ctx.message.sticker) { 249 | directHandler(ctx); 250 | } else { 251 | ctx.reply((ramdb[chatId] && ramdb[chatId].islocked) ? 252 | messages[langSession[chatId]].msg.tasklocked : 253 | messages[langSession[chatId]].msg.start); 254 | } 255 | } 256 | 257 | function i18nHandler(ctx) { 258 | let chatId = ctx.message.chat.id; 259 | let chosen_lang = ctx.message.text.split(' ').slice(1).join('').trim(); 260 | if (config.available_lang.hasOwnProperty(chosen_lang)) { 261 | langSession[chatId] = chosen_lang; 262 | logger(chatId, 'info', 'Changing language to: ' + chosen_lang); 263 | return ctx.reply(messages[langSession[chatId]].msg.language_change); 264 | } 265 | let message = messages[langSession[chatId]].msg.language_available; 266 | let languages_names = ''; 267 | for (let k in config.available_lang) { 268 | if (config.available_lang.hasOwnProperty(k)) { 269 | languages_names += '\n' + '[' + k + '] ' + config.available_lang[k].join(' / '); 270 | } 271 | } 272 | return ctx.reply(message.replace('%languages%', languages_names)); 273 | } 274 | 275 | async function downloadHandler(ctx, fpath) { 276 | let chatId = ctx.message.chat.id; 277 | logger(chatId, 'info', 'Downloading files...'); 278 | for (let fileId of ramdb[chatId].files) { 279 | try { 280 | let { url, ext } = await resolveFile(ctx, fileId); 281 | let destFile = path.join(fpath.srcpath, fileId + ext); 282 | await download(url, destFile); 283 | logger(chatId, 'info', 'File ' + fileId + ' saved to disk.'); 284 | ramdb[chatId].srcimg.push(destFile); 285 | } catch (err) { 286 | logger(chatId, 'error', 'Error downloading file ' + fileId + ': ' + err); 287 | } 288 | } 289 | } 290 | 291 | async function convertHandler(ctx, fpath, format) { 292 | let chatId = ctx.message.chat.id; 293 | logger(chatId, 'info', 'Converting images...'); 294 | for (let src of ramdb[chatId].srcimg) { 295 | try { 296 | let dest = await convert(ctx, src, fpath, format); 297 | ramdb[chatId].destimg.push(dest); 298 | } catch (err) { 299 | logger(chatId, 'error', 'Error converting file ' + src + ': ' + err); 300 | } 301 | } 302 | } 303 | 304 | async function zipHandler(ctx) { 305 | let chatId = ctx.message.chat.id; 306 | logger(chatId, 'info', 'Adding files to ZIP file...'); 307 | let zip = new JSZip(); 308 | for (let src of ramdb[chatId].srcimg) { 309 | let fname = path.join(chatId.toString(), 'src', path.basename(src)); 310 | logger(chatId, 'info', 'Adding file ' + fname); 311 | zip.file(fname, fs.readFileSync(src)); 312 | } 313 | for (let dest of ramdb[chatId].destimg) { 314 | let fname = path.join(chatId.toString(), 'img', path.basename(dest)); 315 | logger(chatId, 'info', 'Adding file ' + fname); 316 | zip.file(fname, fs.readFileSync(dest)); 317 | } 318 | logger(chatId, 'info', 'Packaging files...'); 319 | let content = await zip.generateAsync({ 320 | compression: 'DEFLATE', 321 | type: 'nodebuffer', 322 | comment: 'Created by telegram-stickerimage-bot', 323 | platform: process.platform 324 | }); 325 | return content; 326 | } 327 | 328 | function stickerSetHandler(ctx, setName) { 329 | let chatId = ctx.message.chat.id; 330 | ctx.reply(messages[langSession[chatId]].msg.get_set_info); 331 | bot.telegram.getStickerSet(setName) 332 | .then((set) => { 333 | if (ramdb[chatId].files.length + set.stickers.length >= config.maximages) { 334 | return ctx.reply(messages[langSession[chatId]].msg.taskfull); 335 | } 336 | logger(chatId, 'info', 'Adding Sticker Set: ' + setName); 337 | addSet(ctx, set); 338 | }) 339 | .catch((err) => { 340 | logger(chatId, 'error', 'Error Adding Sticker Set: ' + setName + ': ' + err); 341 | ctx.reply(messages[langSession[chatId]].msg.invalid_set.replace('%setName%', setName)); 342 | }); 343 | } 344 | 345 | async function directHandler(ctx) { 346 | let chatId = ctx.message.chat.id; 347 | let messageId = ctx.message.message_id; 348 | let format = 'jpg'; // Default format 349 | newPackHandler(ctx, () => { }); 350 | ramdb[chatId].islocked = true; 351 | let fpath = { 352 | packpath: path.join(config.file_storage, chatId.toString()) 353 | }; 354 | fpath['srcpath'] = path.join(fpath.packpath, 'src'); 355 | fpath['imgpath'] = path.join(fpath.packpath, 'img'); 356 | fs.mkdirpSync(fpath.packpath); 357 | fs.mkdirpSync(fpath.srcpath); 358 | fs.mkdirpSync(fpath.imgpath); 359 | logger(chatId, 'info', 'Started direct image task.'); 360 | let pendingMsg = await ctx.reply(messages[langSession[chatId]].msg.direct_task_started); 361 | try { 362 | let { url, ext } = await resolveFile(ctx, ctx.message.sticker.file_id); 363 | let destFile = path.join(fpath.srcpath, ctx.message.sticker.file_id + ext); 364 | await download(url, destFile); 365 | // Determine format based on sticker type 366 | if (ctx.message.sticker.is_animated || ctx.message.sticker.is_video) { 367 | format = 'gif'; 368 | } 369 | let outputFile = await convert(ctx, destFile, fpath, format); 370 | // Send the file as a document with disable_content_type_detection, though, doesn't seem to work 371 | await ctx.replyWithDocument({ 372 | source: fs.createReadStream(outputFile), 373 | filename: path.basename(outputFile) 374 | }, { 375 | reply_to_message_id: messageId, 376 | disable_content_type_detection: true 377 | }); 378 | await ctx.deleteMessage(pendingMsg.message_id); 379 | cleanup(chatId); 380 | } catch (err) { 381 | cleanup(chatId); 382 | logger(chatId, 'error', 'Error direct image task:' + err); 383 | await ctx.reply( 384 | messages[langSession[chatId]].msg.error, 385 | { reply_to_message_id: messageId } 386 | ); 387 | } 388 | } 389 | 390 | function addSticker(ctx) { 391 | let chatId = ctx.message.chat.id; 392 | if (ramdb[chatId].files.includes(ctx.message.sticker.file_id)) { 393 | return ctx.reply(messages[langSession[chatId]].msg.duplicated_sticker, { reply_to_message_id: ctx.message.message_id }); 394 | } 395 | if (ramdb[chatId].files.length >= config.maximages) { 396 | return ctx.reply(messages[langSession[chatId]].msg.taskfull); 397 | } 398 | ramdb[chatId].files.push(ctx.message.sticker.file_id); 399 | let remain = config.maximages - ramdb[chatId].files.length; 400 | ctx.reply(remain === 0 ? 401 | messages[langSession[chatId]].msg.taskfull : 402 | messages[langSession[chatId]].msg.saved.replace('%remain%', remain)); 403 | } 404 | 405 | function addSet(ctx, set) { 406 | let chatId = ctx.message.chat.id; 407 | let originCount = ramdb[chatId].files.length; 408 | set.stickers.forEach((s) => { 409 | if (!ramdb[chatId].files.includes(s.file_id)) { 410 | ramdb[chatId].files.push(s.file_id); 411 | } 412 | }); 413 | ctx.reply(messages[langSession[chatId]].msg.set_added_count 414 | .replace('%sticker_count%', ramdb[chatId].files.length - originCount)); 415 | } 416 | 417 | async function resolveFile(ctx, fileId) { 418 | let chatId = ctx.message.chat.id; 419 | try { 420 | let file = await ctx.telegram.getFile(fileId); 421 | let url = `https://api.telegram.org/file/bot${config.token}/${file.file_path}`; 422 | let ext = path.extname(file.file_path) || ''; 423 | return { url, ext }; 424 | } catch (err) { 425 | await ctx.reply( 426 | messages[langSession[chatId]].msg.err_get_filelink.replace('%fileId%', fileId) 427 | ); 428 | logger(chatId, 'error', 'Get File Link for ' + fileId + ': ' + err); 429 | throw err; 430 | } 431 | } 432 | 433 | async function download(url, dest) { 434 | const response = await axios({ 435 | method: 'get', 436 | url: url, 437 | responseType: 'stream', 438 | }); 439 | const writer = fs.createWriteStream(dest); 440 | response.data.pipe(writer); 441 | return new Promise((resolve, reject) => { 442 | writer.on('finish', resolve); 443 | writer.on('error', (err) => { 444 | fs.unlink(dest, () => { }); 445 | reject(err); 446 | }); 447 | }); 448 | } 449 | 450 | async function convert(ctx, src, fpath, format) { 451 | let chatId = ctx.message.chat.id; 452 | let destimg; 453 | let width = 512; // Default width 454 | let ext = path.extname(src).toLowerCase(); 455 | 456 | if (ext === '.tgs' || ext === '.webm') { 457 | // Animated sticker, convert to GIF regardless of the format parameter 458 | destimg = path.join(fpath.imgpath, path.basename(src, ext) + '.gif'); 459 | if (ext === '.tgs') { 460 | await convertTgsToGif(src, destimg, width); 461 | } else { 462 | await convertWebmToGif(src, destimg, width); 463 | } 464 | } else { 465 | // Static image, convert based on the format parameter (jpg or png) 466 | destimg = path.join(fpath.imgpath, path.basename(src, ext) + '.' + format); 467 | await convertImage(src, destimg, width, format); 468 | } 469 | 470 | return destimg; 471 | } 472 | 473 | async function convertWebmToGif(src, dest, width) { 474 | return new Promise((resolve, reject) => { 475 | ffmpeg(src) 476 | .outputOptions([ 477 | '-vf', `scale=${width}:-1:flags=lanczos`, 478 | '-y' 479 | ]) 480 | .toFormat('gif') 481 | .save(dest) 482 | .on('end', resolve) 483 | .on('error', reject); 484 | }); 485 | } 486 | 487 | async function convertTgsToGif(src, dest, width) { 488 | const fs = require('fs-extra'); 489 | const path = require('path'); 490 | const { exec } = require('child_process'); 491 | 492 | // Define paths 493 | const jsonPath = src + '.json'; 494 | 495 | // Decompress the .tgs file to JSON 496 | await new Promise((resolve, reject) => { 497 | const command = `gzip -dc "${src}" > "${jsonPath}"`; 498 | exec(command, (error, stdout, stderr) => { 499 | if (error) { 500 | console.error(`Error decompressing .tgs file: ${stderr}`); 501 | reject(error); 502 | } else { 503 | resolve(); 504 | } 505 | }); 506 | }); 507 | 508 | // Use lottie2gif to convert JSON to GIF 509 | await new Promise((resolve, reject) => { 510 | const command = `${config.lottie2gif} -o "${dest}" "${jsonPath}"`; 511 | exec(command, (error, stdout, stderr) => { 512 | if (error) { 513 | console.error(`Error converting JSON to GIF: ${stderr}`); 514 | reject(error); 515 | } else { 516 | resolve(); 517 | } 518 | }); 519 | }); 520 | 521 | // Clean up the temporary JSON file 522 | fs.unlinkSync(jsonPath); 523 | } 524 | 525 | async function convertImage(src, dest, width, format) { 526 | let image = sharp(src); 527 | if (width && width < 512) { 528 | image = image.resize(width, width, { fit: 'inside' }); 529 | } 530 | if (format === 'png') { 531 | await image.toFile(dest); 532 | } else { 533 | await image.flatten({ background: '#FFFFFF' }).jpeg().toFile(dest); 534 | } 535 | } 536 | 537 | function cleanup(id) { 538 | logger(id, 'info', 'Cleaning up...'); 539 | delete ramdb[id]; 540 | fs.removeSync(path.resolve(config.file_storage, id.toString())); 541 | } 542 | 543 | function loadLang() { 544 | for (let k in config.available_lang) { 545 | if (config.available_lang.hasOwnProperty(k)) { 546 | messages[k] = JSON.parse(fs.readFileSync(path.resolve('./lang/' + k + '.json'), 'utf8')); 547 | logger('INTERNAL', 'info', 'Loaded language strings: ' + k); 548 | } 549 | } 550 | } 551 | 552 | function logger(chatId, type, msg) { 553 | console.log('[' + chatId + ']', '[' + type.toUpperCase() + ']', msg); 554 | } 555 | --------------------------------------------------------------------------------