mirror of
https://github.com/Divam-dev/aynt.git
synced 2025-02-20 11:23:20 +08:00
initial commit
This commit is contained in:
commit
a009b0618a
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/node_modules
|
||||||
|
.env
|
||||||
|
.idea
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Divam
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<div align="center">
|
||||||
|
<h3 align="center">AYNT Bot</h3>
|
||||||
|
<p align="center">
|
||||||
|
AYNT Bot is a Telegram bot designed to help users easily download videos from various social media platforms such as YouTube, TikTok, Instagram, and Twitter.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
AYNT Bot is powered by the Telegraf library and allows users to send video URLs from supported platforms to the bot. The bot then downloads the videos from these URLs and sends them back to the user in telegram. The supported platforms include YouTube, TikTok, Instagram, and Twitter.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
Make sure you have Node.js installed on your system.
|
||||||
|
Install all requirements using npm:
|
||||||
|
```
|
||||||
|
npm i
|
||||||
|
```
|
||||||
|
Create a .env file in the root directory and add your bot token:
|
||||||
|
|
||||||
|
```
|
||||||
|
BOT_TOKEN=your_bot_token_here
|
||||||
|
```
|
||||||
|
If you're running a local server to increase limit from 50 MB to 2 GB, also add the local server URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
LOCAL_SERVER=http://127.0.0.1:8081
|
||||||
|
```
|
||||||
|
How to use local telegram bot api: https://github.com/tdlib/telegram-bot-api
|
||||||
|
|
||||||
|
## Start the bot:
|
||||||
|
```
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
Interact with the bot in your Telegram app. Start a chat with the bot and send it URLs from supported platforms. The bot will download and send back the videos.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `/start` - Initiates a conversation with the bot.
|
||||||
|
- `/help` - Displays a help message with information on how to use the bot.
|
||||||
|
|
||||||
|
## License
|
||||||
|
AYNT Bot is open-source software licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
|
3441
package-lock.json
generated
Normal file
3441
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "aynt",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "AYNT Bot is a Telegram bot designed to help users easily download videos from various social media platforms such as YouTube, TikTok, Instagram, and Twitter.",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "nodemon src/index.js",
|
||||||
|
"start": "node src/index.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/divam-dev/aynt.git"
|
||||||
|
},
|
||||||
|
"author": "divam",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/divam-dev/aynt/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/divam-dev/aynt#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"ffmpeg-static": "^5.1.0",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
|
"instagram-url-dl": "^5.1.3",
|
||||||
|
"nodemon": "^2.0.22",
|
||||||
|
"telegraf": "^4.12.2",
|
||||||
|
"telegraf-session-local": "^2.1.1",
|
||||||
|
"ytdl-core": "^4.11.5"
|
||||||
|
}
|
||||||
|
}
|
21
src/downloaders/instagram_dl.js
Normal file
21
src/downloaders/instagram_dl.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const ig = require('instagram-url-dl');
|
||||||
|
|
||||||
|
async function downloadInstagram(ctx, link) {
|
||||||
|
try {
|
||||||
|
const res = await ig(link);
|
||||||
|
const data = res.data;
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
if (item.type === 'video') {
|
||||||
|
await ctx.telegram.sendVideo(ctx.chat.id, item.url);
|
||||||
|
} else if (item.type === 'image') {
|
||||||
|
await ctx.telegram.sendPhoto(ctx.chat.id, item.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
await ctx.reply('Error downloading the file.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = downloadInstagram;
|
65
src/downloaders/tiktok_dl.js
Normal file
65
src/downloaders/tiktok_dl.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const { Markup } = require('telegraf');
|
||||||
|
|
||||||
|
// Adding useragent to avoid IP bans
|
||||||
|
const headers = {
|
||||||
|
'User-Agent': 'TikTok 26.2.0 rv:262018 (iPhone; iOS 14.4.2; en_US) Cronet'
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVideo = async (url) => {
|
||||||
|
const API_URL = `https://aemt.me/download/tiktokslide?url=${encodeURIComponent(url)}`;
|
||||||
|
const response = await axios.get(API_URL, { headers });
|
||||||
|
const data = response.data;
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(bot) {
|
||||||
|
bot.action(/^download_music_(.+)/, async (ctx) => {
|
||||||
|
const videoId = ctx.match[1];
|
||||||
|
try {
|
||||||
|
const data = await getVideo(`https://www.tiktok.com/@user/video/${videoId}`);
|
||||||
|
ctx.replyWithAudio(data.result.data.music);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
ctx.reply('Error downloading music');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return async function downloadTikTokVideo(ctx, videoUrl) {
|
||||||
|
try {
|
||||||
|
// Always use the full URL with https scheme
|
||||||
|
const fullVideoUrl = `https://${videoUrl}`;
|
||||||
|
const data = await getVideo(fullVideoUrl);
|
||||||
|
|
||||||
|
const mediaUrl = data.result.data.play;
|
||||||
|
const isAudio = mediaUrl.endsWith('.mp3');
|
||||||
|
|
||||||
|
if (isAudio) {
|
||||||
|
// Construct a gallery-like message with multiple images
|
||||||
|
const mediaArray = data.result.data.images.map(image => ({
|
||||||
|
type: 'photo',
|
||||||
|
media: { url: image }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Reply with the media group (gallery-like message)
|
||||||
|
await ctx.replyWithMediaGroup(mediaArray);
|
||||||
|
|
||||||
|
// Send the audio without a button
|
||||||
|
ctx.replyWithAudio(data.result.data.music);
|
||||||
|
} else {
|
||||||
|
// Reply with video
|
||||||
|
const keyboard = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '🎵download music', callback_data: `download_music_${data.result.data.id}` }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await ctx.replyWithVideo(mediaUrl, { reply_markup: keyboard });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
ctx.reply('Error downloading media');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
43
src/downloaders/twitter_dl.js
Normal file
43
src/downloaders/twitter_dl.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
async function downloadTwitterVideo(ctx, url) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`https://aemt.me/download/twtdl?url=${encodeURIComponent(url)}`);
|
||||||
|
|
||||||
|
if (response.data.status && response.data.result.length > 0) {
|
||||||
|
const videoUrl = response.data.result[0].url;
|
||||||
|
|
||||||
|
// Download the video
|
||||||
|
const videoResponse = await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: videoUrl,
|
||||||
|
responseType: 'stream',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the video to a local file
|
||||||
|
const videoFilePath = 'downloaded_video.mp4';
|
||||||
|
const videoStream = fs.createWriteStream(videoFilePath);
|
||||||
|
videoResponse.data.pipe(videoStream);
|
||||||
|
|
||||||
|
videoStream.on('finish', async () => {
|
||||||
|
// Sending the downloaded video to the user
|
||||||
|
await ctx.replyWithVideo({ source: videoFilePath });
|
||||||
|
|
||||||
|
// Clean up: remove the downloaded file
|
||||||
|
fs.unlink(videoFilePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error deleting video file:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await ctx.reply('Error: Unable to download the Twitter video.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading Twitter video:', error);
|
||||||
|
await ctx.reply('Error: Unable to download the Twitter video.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = downloadTwitterVideo;
|
111
src/downloaders/youtube_dl.js
Normal file
111
src/downloaders/youtube_dl.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
const ytdl = require('ytdl-core');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
|
||||||
|
async function downloadYoutubeVideo(ctx, url) {
|
||||||
|
const videoId = ytdl.getURLVideoID(url);
|
||||||
|
const info = await ytdl.getInfo(videoId);
|
||||||
|
const format = ytdl.chooseFormat(info.formats, { quality: 'highestvideo' });
|
||||||
|
const audioFormat = ytdl.chooseFormat(info.formats, { quality: 'highestaudio' });
|
||||||
|
|
||||||
|
// Check if the video is live
|
||||||
|
if (info.videoDetails.isLiveContent) {
|
||||||
|
await ctx.reply('⛔ This video is live, so it cannot be downloaded now. Please try again after the live stream is ended.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the size of the video in bytes
|
||||||
|
let maxVideoSize;
|
||||||
|
if (process.env.LOCAL_SERVER) {
|
||||||
|
// 2GB in bytes (2 * 1024 * 1024 * 1024)
|
||||||
|
maxVideoSize = 2 * 1024 * 1024 * 1024;
|
||||||
|
} else {
|
||||||
|
// 50MB in bytes (50 * 1024 * 1024)
|
||||||
|
maxVideoSize = 50 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the size of the video in bytes
|
||||||
|
const videoSize = parseInt(format.contentLength);
|
||||||
|
if (videoSize > maxVideoSize) {
|
||||||
|
await ctx.reply(`⛔ The video is too large to download (max ${maxVideoSize / (1024 * 1024)}MB).`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get video information for the preview message
|
||||||
|
const videoName = info.videoDetails.title;
|
||||||
|
const views = info.videoDetails.viewCount;
|
||||||
|
const uploadDate = new Date(info.videoDetails.uploadDate).toLocaleDateString();
|
||||||
|
const uploader = info.videoDetails.author.name;
|
||||||
|
const duration = new Date(0);
|
||||||
|
duration.setSeconds(info.videoDetails.lengthSeconds);
|
||||||
|
const durationStr = duration.toISOString().substr(11, 8);
|
||||||
|
|
||||||
|
// Construct the video thumbnail URL
|
||||||
|
const thumbnailUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFXyq4qpAw4IARUAAIhCGAFwAcABBg==&rs=AOn4CLAVrdfOFtJegCDgOPWIT2WTNA3XwQ`;
|
||||||
|
|
||||||
|
// Send video preview message with photo
|
||||||
|
await ctx.replyWithPhoto({ url: thumbnailUrl }, {
|
||||||
|
caption: `<b>${videoName}</b>\n\n👁 ${views}\n📥 ${uploadDate}\n👤 ${uploader}\n🕒 ${durationStr}`,
|
||||||
|
parse_mode: 'HTML'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start downloading
|
||||||
|
await ctx.reply('✅ Start downloading...');
|
||||||
|
|
||||||
|
if (format && audioFormat) {
|
||||||
|
const videoPath = path.join(__dirname, `${videoId}.mp4`);
|
||||||
|
const audioPath = path.join(__dirname, `${videoId}_audio.mp4`);
|
||||||
|
|
||||||
|
const videoReadableStream = ytdl(url, { format: format });
|
||||||
|
const audioReadableStream = ytdl(url, { format: audioFormat });
|
||||||
|
|
||||||
|
const videoWriteStream = fs.createWriteStream(videoPath);
|
||||||
|
const audioWriteStream = fs.createWriteStream(audioPath);
|
||||||
|
|
||||||
|
// Use `pipeline` to merge the video and audio streams into their respective files
|
||||||
|
const { pipeline } = require('stream');
|
||||||
|
await Promise.all([
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
pipeline(videoReadableStream, videoWriteStream, err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
pipeline(audioReadableStream, audioWriteStream, err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Now that the video and audio streams are saved as separate files, merge them using ffmpeg
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
ffmpeg()
|
||||||
|
.input(videoPath)
|
||||||
|
.input(audioPath)
|
||||||
|
.outputOptions('-c:v', 'copy')
|
||||||
|
.outputOptions('-c:a', 'mp3')
|
||||||
|
.on('error', reject)
|
||||||
|
.on('end', resolve)
|
||||||
|
.save(path.join(__dirname, `${videoId}_merged.mp4`));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send "Uploading" message after merging is complete
|
||||||
|
await ctx.reply('✅ Uploading...');
|
||||||
|
|
||||||
|
// Send the merged video to the user
|
||||||
|
const mergedFilePath = path.join(__dirname, `${videoId}_merged.mp4`);
|
||||||
|
await ctx.replyWithVideo({ source: fs.createReadStream(mergedFilePath) });
|
||||||
|
|
||||||
|
// Clean up the temporary files
|
||||||
|
fs.unlinkSync(videoPath);
|
||||||
|
fs.unlinkSync(audioPath);
|
||||||
|
fs.unlinkSync(mergedFilePath);
|
||||||
|
} else {
|
||||||
|
await ctx.reply('No suitable format found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = downloadYoutubeVideo;
|
19
src/helpMessage.json
Normal file
19
src/helpMessage.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"helpMessage": [
|
||||||
|
"@AYNT_Bot is designed to download video and audio tracks from social networks.",
|
||||||
|
"",
|
||||||
|
"How to use:",
|
||||||
|
"",
|
||||||
|
" 1. Go to the page with an interesting video (for example - https://www.youtube.com/watch?v=dQw4w9WgXcQ or https://youtu.be/dQw4w9WgXcQ).",
|
||||||
|
" 2. Send link in chat.",
|
||||||
|
" 3. Bot will send you video in telegram chat.",
|
||||||
|
"",
|
||||||
|
"Currently you can download from:",
|
||||||
|
" - Youtube(in developing)",
|
||||||
|
" - Tiktok",
|
||||||
|
" - Instagram",
|
||||||
|
" - Twitter(videos)(temporarily not working)",
|
||||||
|
"",
|
||||||
|
"Feedback - @DivamYT"
|
||||||
|
]
|
||||||
|
}
|
88
src/index.js
Normal file
88
src/index.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
const { Telegraf } = require('telegraf');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
let bot;
|
||||||
|
if (process.env.LOCAL_SERVER) {
|
||||||
|
bot = new Telegraf(process.env.BOT_TOKEN, { telegram: { apiRoot: process.env.LOCAL_SERVER } });
|
||||||
|
} else {
|
||||||
|
bot = new Telegraf(process.env.BOT_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadYoutubeVideo = require('./downloaders/youtube_dl');
|
||||||
|
const downloadTikTokVideo = require('./downloaders/tiktok_dl')(bot);
|
||||||
|
const downloadInstagram = require('./downloaders/instagram_dl');
|
||||||
|
const downloadTwitterVideo = require('./downloaders/twitter_dl');
|
||||||
|
const helpMessage = require('./helpMessage.json');
|
||||||
|
|
||||||
|
const SECONDS_PER_MINUTE = 60;
|
||||||
|
|
||||||
|
let isBotRunning = false;
|
||||||
|
const userLastLinkTime = {};
|
||||||
|
|
||||||
|
bot.start(async (ctx) => {
|
||||||
|
isBotRunning = true;
|
||||||
|
await ctx.reply(`Hi ${ctx.from.first_name ? ctx.from.first_name : 'user'}! Send me a link:`);
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.help(async (ctx) => {
|
||||||
|
try {
|
||||||
|
const formattedHelpMessage = helpMessage.helpMessage.join('\n');
|
||||||
|
await ctx.reply(formattedHelpMessage, { disable_web_page_preview: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bot.on('text', async (ctx) => {
|
||||||
|
const { id: userId } = ctx.from;
|
||||||
|
const { text, date: messageTime } = ctx.message;
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
if (currentTime - messageTime > SECONDS_PER_MINUTE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userLastLinkTime[userId] && Date.now() - userLastLinkTime[userId] < 5000) {
|
||||||
|
await ctx.reply('You can send a link once in 5 seconds');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const youtubeUrlRegex = /(https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/|https?:\/\/(?:www\.)?youtube\.com\/shorts\/)([\w-]{11})/gi;
|
||||||
|
const tiktokUrlRegex = /(https?:\/\/(?:www\.)?tiktok\.com\/(?:@[\w.-]+\/video\/[\w-]+|@[\w.-]+)|vm\.tiktok\.com\/[\w.-]+|vt\.tiktok\.com\/[\w.-]+)/gi;
|
||||||
|
const instagramUrlRegex = /(https?:\/\/(?:www\.)?instagram\.com\/(p|tv|reels|stories)\/[\w.-]+)/gi;
|
||||||
|
const twitterUrlRegex = /(https?:\/\/(?:www\.)?twitter\.com\/(?:[\w.-]+)\/status\/[\d]+|https?:\/\/t\.co\/[\w.-]+)/gi;
|
||||||
|
|
||||||
|
const youtubeUrls = text.match(youtubeUrlRegex);
|
||||||
|
const tiktokUrls = text.match(tiktokUrlRegex);
|
||||||
|
const instagramUrls = text.match(instagramUrlRegex);
|
||||||
|
|
||||||
|
const twitterUrls = [];
|
||||||
|
let match;
|
||||||
|
while ((match = twitterUrlRegex.exec(text)) !== null) {
|
||||||
|
twitterUrls.push(match[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (youtubeUrls && youtubeUrls.length > 0) {
|
||||||
|
await handleVideoDownload(ctx, youtubeUrls, downloadYoutubeVideo);
|
||||||
|
} else if (tiktokUrls && tiktokUrls.length > 0) {
|
||||||
|
await handleVideoDownload(ctx, tiktokUrls, downloadTikTokVideo);
|
||||||
|
} else if (instagramUrls && instagramUrls.length > 0) {
|
||||||
|
await handleVideoDownload(ctx, instagramUrls, downloadInstagram);
|
||||||
|
} else if (twitterUrls.length > 0) {
|
||||||
|
await handleVideoDownload(ctx, twitterUrls, downloadTwitterVideo);
|
||||||
|
} else {
|
||||||
|
await ctx.reply('Unknown command');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleVideoDownload(ctx, urls, downloaderFn) {
|
||||||
|
for (const url of urls) {
|
||||||
|
await downloaderFn(ctx, url);
|
||||||
|
}
|
||||||
|
userLastLinkTime[ctx.from.id] = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.launch();
|
||||||
|
|
||||||
|
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||||||
|
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
Loading…
x
Reference in New Issue
Block a user