mirror of
https://github.com/Divam-dev/aynt.git
synced 2025-02-20 11:23:20 +08:00
Updated many downloaders
This commit is contained in:
parent
963ac9afed
commit
717d74f742
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Divam
|
||||
Copyright (c) 2024 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
|
||||
|
11
config.json
Normal file
11
config.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"general": {
|
||||
"delayTimeLimit": 5000,
|
||||
"localServer": "",
|
||||
"fileSize": "",
|
||||
"about": {
|
||||
"name": "@AYNT_Bot",
|
||||
"support": "@DivamYT"
|
||||
}
|
||||
}
|
||||
}
|
4060
package-lock.json
generated
4060
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aynt",
|
||||
"version": "1.1.0",
|
||||
"version": "2.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": {
|
||||
@ -25,10 +25,13 @@
|
||||
"ffmpeg-static": "^5.1.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"node-cron": "^3.0.2",
|
||||
"mongodb": "^6.3.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"request": "^2.88.2",
|
||||
"soundcloud-scraper": "^5.0.3",
|
||||
"telegraf": "^4.12.2",
|
||||
"telegraf-session-local": "^2.1.1",
|
||||
"yt-search": "^2.10.4",
|
||||
"ytdl-core": "^4.11.5"
|
||||
}
|
||||
}
|
||||
|
60
src/commands/botStatus.js
Normal file
60
src/commands/botStatus.js
Normal file
@ -0,0 +1,60 @@
|
||||
const os = require('os');
|
||||
|
||||
module.exports = async function statusCommand(ctx) {
|
||||
const statusMessage = generateStatusMessage();
|
||||
|
||||
await ctx.replyWithMarkdown(statusMessage);
|
||||
};
|
||||
|
||||
function fetchDataFromDatabase() {
|
||||
const users = 1000; // Example: number of users
|
||||
const messages = 5000; // Example: number of messages
|
||||
const downloadedGB = 10; // Example: GB downloaded
|
||||
const downloadedFiles = 20; // Example: number of files downloaded
|
||||
return { users, messages, downloadedGB, downloadedFiles };
|
||||
}
|
||||
|
||||
function generateStatusMessage() {
|
||||
const { users, messages, downloadedGB, downloadedFiles } = fetchDataFromDatabase();
|
||||
|
||||
// Remaining code stays the same
|
||||
const uptime = process.uptime();
|
||||
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
|
||||
const nodeVersion = process.version;
|
||||
|
||||
const osName = () => {
|
||||
switch (os.platform()) {
|
||||
case 'aix': return 'AIX';
|
||||
case 'darwin': return 'macOS';
|
||||
case 'freebsd': return 'FreeBSD';
|
||||
case 'linux': return 'Linux';
|
||||
case 'openbsd': return 'OpenBSD';
|
||||
case 'sunos': return 'SunOS';
|
||||
case 'win32': return 'Windows';
|
||||
case 'android': return 'Android';
|
||||
default: return os.platform();
|
||||
}
|
||||
};
|
||||
|
||||
const osVersion = os.release();
|
||||
|
||||
const statusMessage = `
|
||||
🤖 Bot Status:
|
||||
|
||||
🕒 Uptime: ${formatTime(uptime)}
|
||||
👥 Users: ${users}
|
||||
💬 Messages: ${messages}
|
||||
📁 Downloaded Files: ${downloadedFiles}
|
||||
💾 Downloaded GB: ${downloadedGB}
|
||||
💻 OS: ${osName()} ${osVersion}
|
||||
🚀 Node.js Version: ${nodeVersion}
|
||||
`;
|
||||
return statusMessage;
|
||||
}
|
||||
|
||||
function formatTime(seconds) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
return `${hours}h ${minutes}m ${remainingSeconds}s`;
|
||||
}
|
@ -10,9 +10,15 @@
|
||||
"",
|
||||
"Currently you can download from:",
|
||||
" - Youtube(in developing)",
|
||||
" - Youtube Music",
|
||||
" - Spotify",
|
||||
" - SoundCloud",
|
||||
" - Tiktok",
|
||||
" - Instagram",
|
||||
" - Twitter",
|
||||
" - Twitter (X)",
|
||||
" - Facebook",
|
||||
" - Pinterest",
|
||||
" - Reddit",
|
||||
"",
|
||||
"Feedback - @DivamYT"
|
||||
]
|
43
src/database/mongodb.js
Normal file
43
src/database/mongodb.js
Normal file
@ -0,0 +1,43 @@
|
||||
const { MongoClient } = require('mongodb');
|
||||
|
||||
const uri = 'mongodb://localhost:27017'; // Your MongoDB URI
|
||||
const client = new MongoClient(uri);
|
||||
|
||||
async function connectToDatabase() {
|
||||
try {
|
||||
await client.connect();
|
||||
console.log('Connected to MongoDB');
|
||||
} catch (error) {
|
||||
console.error('Error connecting to MongoDB:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertUserData(userData) {
|
||||
const db = client.db('your_database_name');
|
||||
const usersCollection = db.collection('users');
|
||||
|
||||
try {
|
||||
await usersCollection.insertOne(userData);
|
||||
console.log('User data inserted into the database');
|
||||
} catch (error) {
|
||||
console.error('Error inserting user data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateLinkCount(userId) {
|
||||
const db = client.db('your_database_name');
|
||||
const usersCollection = db.collection('users');
|
||||
|
||||
try {
|
||||
await usersCollection.updateOne({ userId }, { $inc: { linksSent: 1 } });
|
||||
console.log('Links count updated for user:', userId);
|
||||
} catch (error) {
|
||||
console.error('Error updating link count:', error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
connectToDatabase,
|
||||
insertUserData,
|
||||
updateLinkCount
|
||||
};
|
BIN
src/downloaders/downloads/7322893773615140101.mp4
Normal file
BIN
src/downloaders/downloads/7322893773615140101.mp4
Normal file
Binary file not shown.
85
src/downloaders/facebook_dl.js
Normal file
85
src/downloaders/facebook_dl.js
Normal file
@ -0,0 +1,85 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
require('util');
|
||||
const { promisify } = require('util');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');``
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
const unlinkAsync = promisify(fs.unlink);
|
||||
|
||||
const parseString = (string) => {
|
||||
try {
|
||||
return JSON.parse(`{"text": "${string}"}`).text;
|
||||
} catch (error) {
|
||||
throw new Error("Error parsing string");
|
||||
}
|
||||
};
|
||||
|
||||
const getFBInfo = async (videoUrl, cookie, useragent) => {
|
||||
const headers = {
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-user": "?1",
|
||||
"sec-ch-ua": '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-fetch-site": "none",
|
||||
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||
"accept-language": "en-GB,en;q=0.9,tr-TR;q=0.8,tr;q=0.7,en-US;q=0.6",
|
||||
authority: "www.facebook.com",
|
||||
"cache-control": "max-age=0",
|
||||
cookie: cookie || "sb=Rn8BYQvCEb2fpMQZjsd6L382; datr=Rn8BYbyhXgw9RlOvmsosmVNT; c_user=100003164630629; _fbp=fb.1.1629876126997.444699739; wd=1920x939; spin.r.1004812505_b.trunk_t.1638730393_s.1_v.2_; xs=28%3A8ROnP0aeVF8XcQ%3A2%3A1627488145%3A-1%3A4916%3A%3AAcWIuSjPy2mlTPuZAeA2wWzHzEDuumXI89jH8a_QIV8; fr=0jQw7hcrFdas2ZeyT.AWVpRNl_4noCEs_hb8kaZahs-jA.BhrQqa.3E.AAA.0.0.BhrQqa.AWUu879ZtCw",
|
||||
"upgrade-insecure-requests": "1",
|
||||
"user-agent": useragent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
|
||||
};
|
||||
|
||||
if (!videoUrl || !videoUrl.trim() || !["facebook.com", "fb.watch"].some(domain => videoUrl.includes(domain))) {
|
||||
throw new Error("Please specify a valid Facebook URL");
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(videoUrl, { headers });
|
||||
|
||||
const sdMatch = data.match(/(?:"browser_native_sd_url"|"playable_url"|sd_src)\s*:\s*"([^"]*)"/);
|
||||
|
||||
if (sdMatch && sdMatch[1]) {
|
||||
return {
|
||||
sd: parseString(sdMatch[1]),
|
||||
};
|
||||
} else {
|
||||
throw new Error("Unable to fetch video information at this time. Please try again");
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error("Unable to fetch video information at this time. Please try again");
|
||||
}
|
||||
};
|
||||
|
||||
const downloadFacebookVideo = async (ctx, facebookVideoUrl, cookie, useragent) => {
|
||||
try {
|
||||
const videoInfo = await getFBInfo(facebookVideoUrl, cookie, useragent);
|
||||
|
||||
const response = await axios({
|
||||
url: videoInfo.sd,
|
||||
method: 'GET',
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
const videoFileName = `facebook_video_${Date.now()}.mp4`;
|
||||
await writeFileAsync(videoFileName, response.data);
|
||||
|
||||
const videoFileSize = fs.statSync(videoFileName).size;
|
||||
if (videoFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The video file is too large to send.');
|
||||
await unlinkAsync(videoFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.replyWithVideo({ source: videoFileName });
|
||||
|
||||
await unlinkAsync(videoFileName);
|
||||
} catch (error) {
|
||||
console.error('Error downloading Facebook video:', error);
|
||||
await ctx.reply('Error downloading Facebook video');
|
||||
}
|
||||
};
|
||||
|
||||
// Export the functions for use in other modules
|
||||
module.exports = downloadFacebookVideo;
|
110
src/downloaders/pinterest_dl.js
Normal file
110
src/downloaders/pinterest_dl.js
Normal file
@ -0,0 +1,110 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
util.promisify(require('child_process').exec);
|
||||
const { promisify } = require('util');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
const unlinkAsync = promisify(fs.unlink);
|
||||
|
||||
async function downloadPinterestPost(ctx, url) {
|
||||
try {
|
||||
// Transform Pinterest link
|
||||
url = url.replace('https://www.pinterest.co.uk' || `https://www.ru.pinterest.com`, 'https://www.pinterest.com');
|
||||
|
||||
// Fetch Pinterest post details from the downloader API
|
||||
const response = await axios.get(`https://pinterestdownloader.io/frontendService/DownloaderService?url=${encodeURIComponent(url)}`);
|
||||
const postData = response.data;
|
||||
|
||||
if (!postData || !postData.source || postData.source !== 'pinterest') {
|
||||
throw new Error('Invalid Pinterest post data');
|
||||
}
|
||||
|
||||
if (postData.medias && postData.medias.length > 0) {
|
||||
// Check if it's a video post
|
||||
const videoMedia = postData.medias.find(media => media.videoAvailable && media.extension === 'mp4');
|
||||
|
||||
if (videoMedia) {
|
||||
const videoUrl = videoMedia.url;
|
||||
const videoFileName = `pinterest_video_${Date.now()}.mp4`;
|
||||
|
||||
// Download the video
|
||||
const videoResponse = await axios.get(videoUrl, { responseType: 'arraybuffer' });
|
||||
await writeFileAsync(videoFileName, videoResponse.data);
|
||||
|
||||
// Check video file size
|
||||
const videoFileSize = fs.statSync(videoFileName).size;
|
||||
if (videoFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The video file is too large to send.');
|
||||
await unlinkAsync(videoFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the video to the user
|
||||
await ctx.replyWithVideo({ source: videoFileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(videoFileName);
|
||||
} else {
|
||||
// Check if it's a GIF post
|
||||
const gifMedia = postData.medias.find(media => media.videoAvailable && media.extension === 'gif');
|
||||
|
||||
if (gifMedia) {
|
||||
const gifUrl = gifMedia.url;
|
||||
const gifFileName = `pinterest_gif_${Date.now()}.gif`;
|
||||
|
||||
// Download the GIF
|
||||
const gifResponse = await axios.get(gifUrl, { responseType: 'arraybuffer' });
|
||||
await writeFileAsync(gifFileName, gifResponse.data);
|
||||
|
||||
// Check GIF file size
|
||||
const gifFileSize = fs.statSync(gifFileName).size;
|
||||
if (gifFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The GIF file is too large to send.');
|
||||
await unlinkAsync(gifFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the GIF to the user
|
||||
await ctx.replyWithDocument({ source: gifFileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(gifFileName);
|
||||
} else if (postData.medias[0].url) {
|
||||
// Photo post
|
||||
const photoUrl = postData.medias[0].url; // Assuming the first media is the photo
|
||||
const photoFileName = `pinterest_photo_${Date.now()}.jpg`;
|
||||
|
||||
// Download the photo
|
||||
const photoResponse = await axios.get(photoUrl, { responseType: 'arraybuffer' });
|
||||
await writeFileAsync(photoFileName, photoResponse.data);
|
||||
|
||||
// Check photo file size
|
||||
const photoFileSize = fs.statSync(photoFileName).size;
|
||||
if (photoFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The photo file is too large to send.');
|
||||
await unlinkAsync(photoFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the photo to the user
|
||||
await ctx.replyWithPhoto({ source: photoFileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(photoFileName);
|
||||
} else {
|
||||
throw new Error('Invalid media format in Pinterest post');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('No media available in the Pinterest post');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error downloading Pinterest post:', error);
|
||||
await ctx.reply('Error downloading Pinterest post');
|
||||
}
|
||||
}
|
||||
|
||||
// Export the function for use in other modules
|
||||
module.exports = downloadPinterestPost;
|
90
src/downloaders/reddit_dl.js
Normal file
90
src/downloaders/reddit_dl.js
Normal file
@ -0,0 +1,90 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
const { promisify } = require('util');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
|
||||
const writeFileAsync = promisify(fs.writeFile);
|
||||
const unlinkAsync = promisify(fs.unlink);
|
||||
|
||||
// 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;
|
||||
// }
|
||||
|
||||
async function downloadRedditPost(ctx, url) {
|
||||
try {
|
||||
// Extract the Reddit post ID from the URL
|
||||
const match = url.match(/reddit\.com\/(?:r\/[^/]+\/comments\/|[^/]+\/comments\/)([\w-]+)/i);
|
||||
if (!match) {
|
||||
throw new Error('Invalid Reddit post URL');
|
||||
}
|
||||
|
||||
const postId = match[1];
|
||||
|
||||
// Fetch post details from Reddit API
|
||||
const response = await axios.get(`https://api.reddit.com/api/info.json?id=t3_${postId}`);
|
||||
const postData = response.data.data.children[0].data;
|
||||
|
||||
// Check if it's a video post
|
||||
if (postData.media && postData.media.reddit_video && postData.media.reddit_video.hls_url) {
|
||||
// Video post
|
||||
const hlsPlaylistUrl = postData.media.reddit_video.hls_url;
|
||||
const cleanHlsPlaylistUrl = hlsPlaylistUrl.split('?')[0];
|
||||
|
||||
const videoFileName = `reddit_video_${postId}.mp4`;
|
||||
|
||||
// Use ffmpeg to download and convert the video
|
||||
await exec(`ffmpeg -i ${cleanHlsPlaylistUrl} -c copy ${videoFileName}`);
|
||||
|
||||
// Check video file size
|
||||
const videoFileSize = fs.statSync(videoFileName).size;
|
||||
if (videoFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The video file is too large to send.');
|
||||
await unlinkAsync(videoFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the video to the user
|
||||
await ctx.replyWithVideo({ source: videoFileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(videoFileName);
|
||||
} else if (postData.url_overridden_by_dest) {
|
||||
// Photo post
|
||||
const photoUrl = postData.url_overridden_by_dest;
|
||||
|
||||
const photoFileName = `reddit_photo_${postId}.jpg`;
|
||||
|
||||
const photoResponse = await axios.get(photoUrl, { responseType: 'arraybuffer' });
|
||||
await writeFileAsync(photoFileName, photoResponse.data);
|
||||
|
||||
// Check photo file size
|
||||
const photoFileSize = fs.statSync(photoFileName).size;
|
||||
if (photoFileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The photo file is too large to send.');
|
||||
await unlinkAsync(photoFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the photo to the user
|
||||
await ctx.replyWithPhoto({ source: photoFileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(photoFileName);
|
||||
} else {
|
||||
throw new Error('Unsupported Reddit post type');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error downloading Reddit post:', error);
|
||||
await ctx.reply('Error downloading Reddit post');
|
||||
}
|
||||
}
|
||||
|
||||
// Export the function for use in other modules
|
||||
module.exports = downloadRedditPost;
|
42
src/downloaders/soundcloud_dl.js
Normal file
42
src/downloaders/soundcloud_dl.js
Normal file
@ -0,0 +1,42 @@
|
||||
const SoundCloud = require("soundcloud-scraper");
|
||||
const fs = require("fs");
|
||||
const { promisify } = require('util');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
const unlinkAsync = promisify(fs.unlink);
|
||||
|
||||
async function downloadSoundCloudMusic(ctx, url) {
|
||||
try {
|
||||
const client = new SoundCloud.Client();
|
||||
const song = await client.getSongInfo(url);
|
||||
|
||||
const stream = await song.downloadProgressive();
|
||||
const fileName = `./${song.title.replace(/[^\w\s]/g, '')}.mp3`;
|
||||
|
||||
const writeStream = fs.createWriteStream(fileName);
|
||||
|
||||
stream.pipe(writeStream);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
|
||||
const fileSize = fs.statSync(fileName).size;
|
||||
|
||||
if (fileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The audio file is too large to send.');
|
||||
await unlinkAsync(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.replyWithAudio({ source: fileName });
|
||||
|
||||
// Remove the downloaded file
|
||||
await unlinkAsync(fileName);
|
||||
} catch (error) {
|
||||
console.error('Error downloading SoundCloud music:', error);
|
||||
await ctx.reply('Error downloading SoundCloud music. Please try again later.');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = downloadSoundCloudMusic;
|
227
src/downloaders/spotify_dl.js
Normal file
227
src/downloaders/spotify_dl.js
Normal file
@ -0,0 +1,227 @@
|
||||
const ytdl = require('ytdl-core');
|
||||
const ytSearch = require('yt-search');
|
||||
const axios = require('axios');
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const fs = require('fs');
|
||||
const ffmpegPath = require('ffmpeg-static');
|
||||
const { spawn } = require('child_process');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
|
||||
// Spotify API credentials
|
||||
const clientId = 'acc6302297e040aeb6e4ac1fbdfd62c3';
|
||||
const clientSecret = '0e8439a1280a43aba9a5bc0a16f3f009';
|
||||
const authString = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||||
const tokenUrl = 'https://accounts.spotify.com/api/token';
|
||||
|
||||
const findYouTubeVideo = async (songName, artistName) => {
|
||||
const searchTerms = `${songName} ${artistName}`;
|
||||
const searchResults = await ytSearch(searchTerms);
|
||||
|
||||
if (searchResults && searchResults.videos && searchResults.videos.length > 0) {
|
||||
return searchResults.videos[0].url;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const downloadYouTubeVideo = async (videoUrl, outputFileName, artworkFileName) => {
|
||||
try {
|
||||
const videoInfo = await ytdl.getBasicInfo(videoUrl);
|
||||
|
||||
if (!videoInfo.formats || videoInfo.formats.length === 0) {
|
||||
console.error('No video formats found.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const ffmpegCommand = ffmpeg({ timeout: 10 * 60 })
|
||||
.input(ytdl(videoUrl, { quality: 'highestaudio' }))
|
||||
.audioBitrate(256)
|
||||
.audioFilter('volume=0.3');
|
||||
|
||||
if (artworkFileName) {
|
||||
ffmpegCommand.input(artworkFileName);
|
||||
}
|
||||
|
||||
ffmpegCommand
|
||||
.save(outputFileName)
|
||||
.format('mp3');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ffmpegCommand
|
||||
.on('end', resolve)
|
||||
.on('error', reject);
|
||||
});
|
||||
|
||||
const stats = fs.statSync(outputFileName);
|
||||
const fileSizeInBytes = stats.size;
|
||||
|
||||
if (fileSizeInBytes > maxVideoSize) {
|
||||
console.error('Downloaded file size exceeds the maximum limit.');
|
||||
|
||||
// Delete the downloaded file
|
||||
await fs.promises.unlink(outputFileName);
|
||||
|
||||
// Delete the album artwork file if it exists
|
||||
if (artworkFileName && fs.existsSync(artworkFileName)) {
|
||||
try {
|
||||
await fs.promises.unlink(artworkFileName);
|
||||
console.log('Deleted album artwork file.');
|
||||
} catch (unlinkError) {
|
||||
console.error('Error deleting album artwork file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return outputFileName;
|
||||
} catch (error) {
|
||||
console.error('Error in downloadYouTubeVideo:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Spotify API function to get data
|
||||
const getSpotifyTrackInfo = async (spotifyTrackUrl) => {
|
||||
const trackId = extractTrackIdFromUrl(spotifyTrackUrl);
|
||||
const accessToken = await getToken();
|
||||
|
||||
const trackUrl = `https://api.spotify.com/v1/tracks/${trackId}`;
|
||||
const trackResponse = await axios.get(trackUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const albumId = trackResponse.data.album.id;
|
||||
const albumInfo = await getSpotifyAlbumInfo(albumId, accessToken);
|
||||
|
||||
return {
|
||||
songName: trackResponse.data.name,
|
||||
artistName: trackResponse.data.artists[0].name,
|
||||
albumName: albumInfo.name,
|
||||
albumArtworkUrl: albumInfo.images.length > 0 ? albumInfo.images[0].url : null,
|
||||
};
|
||||
};
|
||||
|
||||
const getSpotifyAlbumInfo = async (albumId, accessToken) => {
|
||||
const albumUrl = `https://api.spotify.com/v1/albums/${albumId}`;
|
||||
const albumResponse = await axios.get(albumUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
return albumResponse.data;
|
||||
};
|
||||
|
||||
const extractTrackIdFromUrl = (spotifyTrackUrl) => {
|
||||
const match = spotifyTrackUrl.match(/\/track\/(\w+)/);
|
||||
return match ? match[1] : null;
|
||||
};
|
||||
|
||||
const getToken = async () => {
|
||||
const response = await axios.post(
|
||||
tokenUrl,
|
||||
'grant_type=client_credentials',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Basic ${authString}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return response.data.access_token;
|
||||
};
|
||||
|
||||
const downloadSpotifyMusic = async (ctx, url) => {
|
||||
try {
|
||||
|
||||
const { songName, artistName, albumArtworkUrl } = await getSpotifyTrackInfo(url);
|
||||
|
||||
const videoUrl = await findYouTubeVideo(songName, artistName);
|
||||
|
||||
if (videoUrl) {
|
||||
|
||||
const downloadedFileName = 'song.mp3';
|
||||
|
||||
const artworkFileName = 'album_artwork.jpg';
|
||||
if (albumArtworkUrl) {
|
||||
const artworkResponse = await axios.get(albumArtworkUrl, { responseType: 'arraybuffer' });
|
||||
fs.writeFileSync(artworkFileName, Buffer.from(artworkResponse.data));
|
||||
}
|
||||
|
||||
const downloadedFilePath = await downloadYouTubeVideo(videoUrl, downloadedFileName, artworkFileName);
|
||||
|
||||
if (!downloadedFilePath) {
|
||||
await ctx.reply('Error: Video size exceeds the maximum limit.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Download completed.');
|
||||
|
||||
const outputFileName = `${songName} (${artistName}).mp3`;
|
||||
|
||||
const mp3FilePath = downloadedFilePath;
|
||||
const imageFilePath = 'album_artwork.jpg';
|
||||
const outputFilePath = outputFileName;
|
||||
|
||||
const ffmpegCommand = [
|
||||
'-i', mp3FilePath,
|
||||
'-i', imageFilePath,
|
||||
'-map', '0:0',
|
||||
'-map', '1:0',
|
||||
'-c', 'copy',
|
||||
'-id3v2_version', '3',
|
||||
'-metadata:s:v', 'title=Album cover',
|
||||
outputFilePath
|
||||
];
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
console.log('Starting FFmpeg process...');
|
||||
const ffmpegProcess = spawn(ffmpegPath, ffmpegCommand, { stdio: 'ignore' });
|
||||
|
||||
ffmpegProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('FFmpeg process completed.');
|
||||
resolve();
|
||||
} else {
|
||||
console.error(`FFmpeg process exited with code ${code}`);
|
||||
reject(`FFmpeg process exited with code ${code}`);
|
||||
}
|
||||
});
|
||||
|
||||
ffmpegProcess.on('error', (err) => {
|
||||
console.error('FFmpeg process error:', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`MP3 file merged with image. Output file: ${outputFilePath}`);
|
||||
|
||||
await ctx.replyWithAudio({ source: outputFilePath });
|
||||
|
||||
console.log('Sent the merged file to the user.');
|
||||
|
||||
try {
|
||||
await fs.promises.unlink(mp3FilePath);
|
||||
await fs.promises.unlink(imageFilePath);
|
||||
await fs.promises.unlink(outputFilePath);
|
||||
console.log('Removed the downloaded files.');
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing downloaded files:', unlinkError);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('No video found.');
|
||||
await ctx.reply('No video found.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
await ctx.reply(`Error: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = downloadSpotifyMusic;
|
@ -1,6 +1,4 @@
|
||||
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'
|
||||
@ -9,8 +7,7 @@ const headers = {
|
||||
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;
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = function(bot) {
|
||||
@ -25,10 +22,12 @@ module.exports = function(bot) {
|
||||
}
|
||||
});
|
||||
|
||||
return async function downloadTikTokVideo(ctx, videoUrl) {
|
||||
async function downloadTikTokVideo(ctx, videoUrl) {
|
||||
try {
|
||||
const fullVideoUrl = `https://${videoUrl}`;
|
||||
const data = await getVideo(fullVideoUrl);
|
||||
// const fullVideoUrl = `https://${videoUrl}`;
|
||||
// const data = await getVideo(fullVideoUrl);
|
||||
|
||||
const data = await getVideo(videoUrl);
|
||||
|
||||
const mediaUrl = data.result.data.play;
|
||||
const isAudio = mediaUrl.endsWith('.mp3');
|
||||
@ -60,5 +59,7 @@ module.exports = function(bot) {
|
||||
console.error(err);
|
||||
ctx.reply('Error downloading media');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return downloadTikTokVideo;
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
// Function to get the media information from a Twitter URL.
|
||||
async function getTwitterMedia(url, options = {}) {
|
||||
let input = {};
|
||||
@ -65,15 +65,6 @@ async function convertXToTwitterURL(url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Function to download Twitter media and send it through Telegram.
|
||||
async function downloadTwitterMedia(ctx, url) {
|
||||
try {
|
||||
|
@ -3,6 +3,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const ffmpegPath = require('ffmpeg-static');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
@ -18,16 +19,6 @@ async function downloadYoutubeVideo(ctx, url) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Max video size
|
||||
const videoSize = parseInt(format.contentLength);
|
||||
if (videoSize > maxVideoSize) {
|
||||
|
56
src/downloaders/youtube_music_dl.js
Normal file
56
src/downloaders/youtube_music_dl.js
Normal file
@ -0,0 +1,56 @@
|
||||
const ytdl = require('ytdl-core');
|
||||
const fs = require('fs');
|
||||
const { promisify } = require('util');
|
||||
const { maxVideoSize } = require('../handlers/links_handler');
|
||||
promisify(fs.writeFile);
|
||||
const unlinkAsync = promisify(fs.unlink);
|
||||
|
||||
async function downloadYoutubeMusic(ctx, url) {
|
||||
try {
|
||||
const videoIdMatch = url.match(/(?:watch\?v=|\/)([\w-]{11})/);
|
||||
if (!videoIdMatch) {
|
||||
throw new Error('Invalid YouTube Music link');
|
||||
}
|
||||
|
||||
const videoId = videoIdMatch[1];
|
||||
|
||||
const info = await ytdl.getInfo(videoId);
|
||||
const audioFormat = ytdl.chooseFormat(info.formats, { filter: 'audioonly' });
|
||||
|
||||
if (!audioFormat) {
|
||||
await ctx.reply('Error: No audio format found for the given YouTube Music video.');
|
||||
return;
|
||||
}
|
||||
|
||||
const audioStream = ytdl(videoId, { format: audioFormat });
|
||||
|
||||
const fileName = `${info.videoDetails.title.replace(/[^\w\s]/g, '')}.mp3`;
|
||||
|
||||
const writeStream = fs.createWriteStream(fileName);
|
||||
|
||||
audioStream.pipe(writeStream);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
writeStream.on('finish', resolve);
|
||||
writeStream.on('error', reject);
|
||||
});
|
||||
|
||||
const fileSize = fs.statSync(fileName).size;
|
||||
|
||||
if (fileSize > maxVideoSize) {
|
||||
await ctx.reply('Error: The audio file is too large to send.');
|
||||
await unlinkAsync(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.replyWithAudio({ source: fileName });
|
||||
|
||||
//remove the downloaded file
|
||||
await unlinkAsync(fileName);
|
||||
} catch (error) {
|
||||
console.error('Error downloading YouTube Music:', error);
|
||||
await ctx.reply('Error downloading YouTube Music. Please try again later.');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = downloadYoutubeMusic;
|
21
src/handlers/links_handler.js
Normal file
21
src/handlers/links_handler.js
Normal file
@ -0,0 +1,21 @@
|
||||
const config = require("../../config.json");
|
||||
let maxVideoSize;
|
||||
|
||||
if (!config.general.localServer) {
|
||||
// If localServer is empty, set maxVideoSize to 50MB
|
||||
maxVideoSize = 50 * 1024 * 1024;
|
||||
} else {
|
||||
// If localServer is not empty, set maxVideoSize to 2GB
|
||||
maxVideoSize = 2 * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
// If fileSize is defined and smaller than maxVideoSize, override maxVideoSize
|
||||
if (config.general.fileSize && config.general.fileSize < maxVideoSize) {
|
||||
maxVideoSize = config.general.fileSize;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
maxVideoSize,
|
||||
};
|
||||
|
||||
|
93
src/index.js
93
src/index.js
@ -1,22 +1,25 @@
|
||||
const { Telegraf } = require('telegraf');
|
||||
const {Telegraf} = require('telegraf');
|
||||
require('dotenv').config();
|
||||
const cron = require('node-cron');
|
||||
const statusCommand = require('./commands/botStatus');
|
||||
const { MongoClient } = require('mongodb');
|
||||
const config = require('../config.json');
|
||||
|
||||
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 bot = config.general.localServer
|
||||
? new Telegraf(process.env.BOT_TOKEN, { telegram: { apiRoot: config.general.localServer } })
|
||||
: 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;
|
||||
const downloadYoutubeMusic = require('./downloaders/youtube_music_dl');
|
||||
const downloadRedditPost = require('./downloaders/reddit_dl');
|
||||
const downloadPinterestPost = require('./downloaders/pinterest_dl');
|
||||
const downloadSoundCloudMusic = require('./downloaders/soundcloud_dl');
|
||||
const downloadFacebookVideo = require('./downloaders/facebook_dl');
|
||||
const downloadSpotifyMusic = require('./downloaders/spotify_dl');
|
||||
|
||||
const helpMessage = require('./commands/helpMessage.json');
|
||||
let isBotRunning = false;
|
||||
const userLastLinkTime = {};
|
||||
|
||||
@ -28,62 +31,72 @@ bot.start(async (ctx) => {
|
||||
bot.help(async (ctx) => {
|
||||
try {
|
||||
const formattedHelpMessage = helpMessage.helpMessage.join('\n');
|
||||
await ctx.reply(formattedHelpMessage, { disable_web_page_preview: true });
|
||||
await ctx.reply(formattedHelpMessage, {disable_web_page_preview: true});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
bot.command('status', statusCommand);
|
||||
|
||||
bot.on('text', async (ctx) => {
|
||||
const { id: userId } = ctx.from;
|
||||
const { text, date: messageTime } = ctx.message;
|
||||
const {id: userId} = ctx.from;
|
||||
const {text, date: messageTime} = ctx.message;
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
if (currentTime - messageTime > SECONDS_PER_MINUTE) {
|
||||
if (currentTime - messageTime > 60) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userLastLinkTime[userId] && Date.now() - userLastLinkTime[userId] < 5000) {
|
||||
await ctx.reply('You can send a link once in 5 seconds');
|
||||
let delayTime = config.general.delayTimeLimit;
|
||||
|
||||
if (delayTime !== 0 && userLastLinkTime[userId] && Date.now() - userLastLinkTime[userId] < delayTime) {
|
||||
await ctx.reply(`You can send a link once in ${delayTime/1000} 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|reel|reels|stories)\/([\w.-]+)/gi;
|
||||
const twitterUrlRegex = /(https?:\/\/(?:www\.)?(?:twitter|x)\.com\/(?:[\w.-]+)\/status\/[\d]+|https?:\/\/t\.co\/[\w.-]+)/gi;
|
||||
const youtubeUrls = text.match (/(https?:\/\/(?:www\.)?youtube\.com\/watch\?v=|https?:\/\/youtu\.be\/|https?:\/\/(?:www\.)?youtube\.com\/shorts\/)([\w-]{11})/gi);
|
||||
const tiktokUrls = text.match (/(https?:\/\/(?:www\.)?tiktok\.com\/(?:@[\w.-]+\/video\/[\w-]+|@[\w.-]+)|vm\.tiktok\.com\/[\w.-]+|vt\.tiktok\.com\/[\w.-]+)/gi);
|
||||
const instagramUrls = text.match (/https?:\/\/(?:www\.)?instagram\.com\/(?:([^\/]+)\/)?(?:p|tv|reel|reels|stories)\/([\w.-]+)/gi);
|
||||
const twitterUrls = text.match (/(https?:\/\/(?:www\.)?(?:twitter|x)\.com\/(?:[\w.-]+)\/status\/.+|https?:\/\/t\.co\/.+)/gi);
|
||||
const youtubeMusicUrls = text.match (/https?:\/\/music\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&(?:list|si)=([a-zA-Z0-9_-]+))?/gi);
|
||||
const redditUrls = text.match(/(https?:\/\/(?:www\.)?reddit\.com\/(?:r\/[^/]+\/comments\/|[^/]+\/comments\/)[\w-]+)/gi);
|
||||
const pinterestUrls= text.match (/(https?:\/\/(?:www\.)?(?:ru\.)?pinterest\.(com|co.uk)\/pin\/[\w.-]+)/gi);
|
||||
const soundcloudUrls = text.match(/https?:\/\/soundcloud\.com\/[\w.-]+\/[\w.-]+/gi);
|
||||
const facebookUrls = text.match(/https?:\/\/(?:www\.|)facebook\.com\/(?:watch\?v=\d+|[^\/]+\/videos\/\d+\/?|permalink\.php\?story_fbid=\d+&id=\d+|fb\.watch\/[a-zA-Z0-9_-]+\/?)\b/gi);
|
||||
const spotifyUrls = text.match(/https?:\/\/open\.spotify\.com\/track\/[a-zA-Z0-9]{22}/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);
|
||||
if (redditUrls && redditUrls.length > 0) {
|
||||
await handleDownload(ctx, redditUrls, downloadRedditPost);
|
||||
} else if (youtubeMusicUrls && youtubeMusicUrls.length > 0) {
|
||||
await handleDownload(ctx, youtubeMusicUrls, downloadYoutubeMusic);
|
||||
} else if (youtubeUrls && youtubeUrls.length > 0) {
|
||||
await handleDownload(ctx, youtubeUrls, downloadYoutubeVideo);
|
||||
} else if (tiktokUrls && tiktokUrls.length > 0) {
|
||||
await handleVideoDownload(ctx, tiktokUrls, downloadTikTokVideo);
|
||||
await handleDownload(ctx, tiktokUrls, downloadTikTokVideo);
|
||||
} else if (instagramUrls && instagramUrls.length > 0) {
|
||||
await handleVideoDownload(ctx, instagramUrls, downloadInstagram);
|
||||
} else if (twitterUrls.length > 0) {
|
||||
await handleVideoDownload(ctx, twitterUrls, downloadTwitterVideo);
|
||||
await handleDownload(ctx, instagramUrls, downloadInstagram);
|
||||
} else if (twitterUrls && twitterUrls.length > 0) {
|
||||
await handleDownload(ctx, twitterUrls, downloadTwitterVideo);
|
||||
} else if (pinterestUrls && pinterestUrls.length > 0) {
|
||||
await handleDownload(ctx, pinterestUrls, downloadPinterestPost);
|
||||
} else if (soundcloudUrls && soundcloudUrls.length > 0) {
|
||||
await handleDownload(ctx, soundcloudUrls, downloadSoundCloudMusic);
|
||||
} else if (facebookUrls && facebookUrls.length > 0) {
|
||||
await handleDownload(ctx, facebookUrls, downloadFacebookVideo);
|
||||
} else if (spotifyUrls && spotifyUrls.length > 0) {
|
||||
await handleDownload(ctx, spotifyUrls, downloadSpotifyMusic);
|
||||
} else {
|
||||
await ctx.reply('Unknown command');
|
||||
}
|
||||
});
|
||||
|
||||
async function handleVideoDownload(ctx, urls, downloaderFn) {
|
||||
async function handleDownload(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'));
|
||||
|
0
tests/regexTest.js
Normal file
0
tests/regexTest.js
Normal file
Loading…
x
Reference in New Issue
Block a user