initial commit

This commit is contained in:
Divam 2023-08-29 01:36:18 +03:00
commit a009b0618a
11 changed files with 3885 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules
.env
.idea

21
LICENSE Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View 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"
}
}

View 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;

View 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');
}
};
};

View 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;

View 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
View 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
View 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'));