add files

This commit is contained in:
fullgream 2025-10-15 04:28:26 +03:00
parent 3bf02993f6
commit ca759ef134
7 changed files with 2409 additions and 0 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
BOT_TOKEN=
ADMIN_LIST=

1
Dockerfile Normal file
View File

@ -0,0 +1 @@

View File

67
src/bot.js Normal file
View File

@ -0,0 +1,67 @@
const TelegramBot = require("node-telegram-bot-api");
const fs = require("fs");
const DownloadVideo = require("./download");
const config = {
token: process.env.BOT_TOKEN,
admins: process.env.ADMIN_LIST?.split(/\s*,\s*/g).map(x => +x) ?? [],
};
config.whitelist = [].concat(
process.env.WHITELIST?.split(/\s*,\s*/g).map(x => +x) ?? [],
config.admins,
);
config.whitelist = Object.keys(
Object.fromEntries(config.whitelist.map(x => [x, true]))
).map(x => +x);
console.info(config);
config.admins = Object.fromEntries(config.admins.map(x => [x, true]));
config.whitelist = Object.fromEntries(config.whitelist.map(x => [x, true]));
const ytLinkRegexp = /^https:\/\/www\.youtube\.com/m;
class TgBot {
constructor () {
this.bot = new TelegramBot(config.token, {
polling: true
});
this.bot.on('text', m => this.newMessageHandler(m));
}
async newMessageHandler (msg) {
console.debug(msg);
if (msg.chat.id === msg.from.id) {
if (config.whitelist[msg.from.id]) {
if (ytLinkRegexp.test(msg.text)) {
const video = new DownloadVideo(msg.text);
// Download only video with best quality
this.download(msg, video);
} else {
this.bot.sendMessage(msg.chat.id, "Invalid youtube link");
}
}
else {
this.bot.sendMessage(msg.chat.id, "Permission denied");
}
}
}
async download (msg, video, quality=DownloadVideo.qualities.mp4_best) {
this.bot.sendMessage(msg.chat.id, "Start download");
await video.setupQuality(quality, false);
video.download()
.then(downloaded => {
if (/^video\//m.test(downloaded.format.mime)) {
this.bot.sendVideo(msg.chat.id, downloaded.data, { caption: "Download complete" }, {
filename: `downloaded.${downloaded.format.exc}`,
contentType: downloaded.format.mime
});
}
})
.catch(() => {
this.bot.sendMessage(msg.chat.id, "Download failed");
});
}
}
const bot = new TgBot();

130
src/download.js Normal file
View File

@ -0,0 +1,130 @@
const { spawn } = require("child_process");
const formatsCache = new Map();
class DownloadVideo {
static get qualities () {
const q = {
webm_best: { exc: "webm", flag: "best", mime: "video/webm" },
mp4_best: { exc: "mp4", flag: "best[ext=mp4]", mime: "video/mp4" },
};
q.default = q.webm_best;
return q;
}
constructor(url, quality) {
const CLASS = this.__proto__.constructor;
this.url = url;
this.quality = quality;
if (!this.quality) this.quality = CLASS.qualities.default;
}
async getFormats() {
const fromCache = formatsCache.get(this.url);
if (fromCache) return fromCache;
const result = await new Promise((resolve, reject) => {
const child = spawn("yt-dlp", ["-j", this.url]);
let data = "";
child.stdout.on("data", (chunk) => (data += chunk.toString()));
child.on("close", (code) => {
if (code !== 0) return reject(new Error("yt-dlp failed"));
const info = JSON.parse(data);
resolve(info.formats);
});
});
formatsCache.set(this.url, result);
return result;
}
async qualities() {
const formats = await this.getFormats();
const q = {};
// Перебираем форматы и группируем по расширению
formats.forEach(f => {
if (!f.ext || !f.format_id) return;
//if (!q[f.ext]) q[f.ext] = [];
const resolution = f.height ? `${f.height}p` : 'audio-only';
let flag = `bestaudio[ext=${f.ext}]`;
if (f.height) {
flag = `best[height<=${f.height}][ext=${f.ext}]`;
}
//const flag = f.format_id;
q[f.ext + "_" + resolution] = {
format_id: f.format_id,
flag, // <- ключевой момент для yt-dlp
exc: f.ext, // <- расширение
resolution,
filesize: f.filesize || null,
};
});
return q;
}
async setupQuality (quality, check=true) {
if (!check) {
this.quality = quality;
return true;
}
const CLASS = this.__proto__.constructor;
const all = Object.assign({}, (await this.qualities()), CLASS.qualities);
if (all[quality.exc + "_" + quality.resolution]) {
this.quality = quality;
return true;
}
return false;
}
async download(cb) {
const data = await new Promise((resolve, reject) => {
const params = [
"-f",
this.quality.flag,
"-o",
"-",
this.url,
];
const child = spawn("yt-dlp", params);
const chunks = [];
let downloaded = 0;
child.stdout.on("data", (chunk) => {
chunks.push(chunk);
downloaded += chunk.length;
//if (cb) cb({ downloaded });
});
child.stderr.on("data", (data) => {
const str = data.toString();
const match = str.match(/(\d+\.\d+)%/);
if (match && cb) {
cb({ progress: parseFloat(match[1]), downloaded });
}
});
child.on("close", (code) => {
if (code === 0) {
resolve(Buffer.concat(chunks));
} else {
reject(new Error(`yt-dlp exited with code ${code}`));
}
});
});
return {
format: this.quality,
data,
};
}
}
module.exports = DownloadVideo;

2204
src/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

5
src/package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"node-telegram-bot-api": "^0.66.0"
}
}