update to 2.0
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
# config.py
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
@@ -19,8 +20,14 @@ COOKIES_FILE = os.getenv("COOKIES_FILE")
|
|||||||
MAX_DURATION_SECONDS = int(os.getenv("MAX_DURATION_SECONDS", 1800))
|
MAX_DURATION_SECONDS = int(os.getenv("MAX_DURATION_SECONDS", 1800))
|
||||||
RATE_LIMIT_SECONDS = int(os.getenv("RATE_LIMIT_SECONDS", 20))
|
RATE_LIMIT_SECONDS = int(os.getenv("RATE_LIMIT_SECONDS", 20))
|
||||||
MAX_FILE_SIZE_MB = 2000
|
MAX_FILE_SIZE_MB = 2000
|
||||||
CACHE_MAX_AGE_DAYS = 7 # Удалять файлы старше 7 дней
|
CACHE_MAX_AGE_DAYS = 7
|
||||||
CACHE_MAX_SIZE_MB = 4096 # Максимальный размер кэша 1 ГБ (0 = без ограничения)
|
CACHE_MAX_SIZE_MB = 4096
|
||||||
|
|
||||||
|
# Авто-качества для TikTok и Instagram (без выбора)
|
||||||
|
DEFAULT_TIKTOK_VIDEO_QUALITY = "1080"
|
||||||
|
DEFAULT_TIKTOK_AUDIO_QUALITY = "192" # кбит/с
|
||||||
|
DEFAULT_INSTAGRAM_VIDEO_QUALITY = "1080"
|
||||||
|
|
||||||
# Private mode
|
# Private mode
|
||||||
ALLOWED_USERS = set(
|
ALLOWED_USERS = set(
|
||||||
int(uid.strip())
|
int(uid.strip())
|
||||||
@@ -30,4 +37,4 @@ ALLOWED_USERS = set(
|
|||||||
|
|
||||||
# Ensure directories exist
|
# Ensure directories exist
|
||||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||||
os.makedirs(TMP_DIR, exist_ok=True)
|
os.makedirs(TMP_DIR, exist_ok=True)
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
|
# downloader.py
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
import json
|
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from config import (
|
||||||
|
DEFAULT_TIKTOK_VIDEO_QUALITY,
|
||||||
|
DEFAULT_TIKTOK_AUDIO_QUALITY,
|
||||||
|
DEFAULT_INSTAGRAM_VIDEO_QUALITY,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +30,7 @@ def _progress_hook(cancel_event, progress_cb):
|
|||||||
return hook
|
return hook
|
||||||
|
|
||||||
|
|
||||||
# ---------------- STANDARD VIDEO ----------------
|
# ---------------- STANDARD VIDEO (YouTube/VK) --------------
|
||||||
|
|
||||||
def download_video(
|
def download_video(
|
||||||
url: str,
|
url: str,
|
||||||
@@ -55,64 +60,86 @@ def download_video(
|
|||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
|
|
||||||
# ---------------- ORIGINAL QUALITY ----------------
|
# ---------------- TikTok: Видео + Аудио (автоматически) --------------
|
||||||
|
|
||||||
def download_original_quality(
|
def download_tiktok_video_and_audio(
|
||||||
url: str,
|
url: str,
|
||||||
out_path: str,
|
video_path: str,
|
||||||
|
audio_path: str,
|
||||||
cookies: str | None,
|
cookies: str | None,
|
||||||
cancel_event: threading.Event,
|
cancel_event: threading.Event,
|
||||||
progress_cb,
|
progress_cb,
|
||||||
):
|
):
|
||||||
"""Скачивает видео в оригинальном качестве"""
|
"""
|
||||||
ydl_opts = {
|
Скачивает TikTok: видео (1080p) + аудио (MP3 192kbps).
|
||||||
"format": "best",
|
Возвращает (video_path, audio_path) или None.
|
||||||
"outtmpl": out_path,
|
"""
|
||||||
|
# 1. Скачиваем видео в 1080p
|
||||||
|
video_opts = {
|
||||||
|
"format": f"bestvideo[height<={DEFAULT_TIKTOK_VIDEO_QUALITY}]+bestaudio/best",
|
||||||
|
"outtmpl": video_path.replace('.mp4', ''),
|
||||||
"merge_output_format": "mp4",
|
"merge_output_format": "mp4",
|
||||||
"cookiefile": cookies,
|
"cookiefile": cookies,
|
||||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||||
"quiet": True,
|
"quiet": True,
|
||||||
"no_warnings": True,
|
"no_warnings": True,
|
||||||
"extractor_args": {
|
"extractor_args": {"tiktok": {"skip_impersonation": True}},
|
||||||
"tiktok": {"skip_impersonation": True},
|
|
||||||
"instagram": {"skip_impersonation": True},
|
|
||||||
},
|
|
||||||
"format_sort": ["quality", "res", "codec", "size"],
|
|
||||||
"concurrent_fragment_downloads": 4,
|
"concurrent_fragment_downloads": 4,
|
||||||
"http_chunk_size": 10485760,
|
"http_chunk_size": 10485760,
|
||||||
}
|
}
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(video_opts) as ydl:
|
||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
|
# Проверяем, что видео создано
|
||||||
|
if not os.path.exists(video_path):
|
||||||
|
raise Exception("TikTok video file not created")
|
||||||
|
|
||||||
# ---------------- TIKTOK MUSIC ONLY ----------------
|
# 2. Извлекаем аудио в MP3
|
||||||
|
audio_opts = {
|
||||||
def download_tiktok_music(
|
|
||||||
url: str,
|
|
||||||
out_path: str,
|
|
||||||
cookies: str | None,
|
|
||||||
cancel_event: threading.Event,
|
|
||||||
progress_cb,
|
|
||||||
):
|
|
||||||
"""Скачивает только звук из TikTok"""
|
|
||||||
ydl_opts = {
|
|
||||||
"format": "bestaudio/best",
|
"format": "bestaudio/best",
|
||||||
"outtmpl": out_path.replace('.mp3', ''),
|
"outtmpl": audio_path.replace('.mp3', ''),
|
||||||
"cookiefile": cookies,
|
"cookiefile": cookies,
|
||||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
"postprocessors": [{
|
||||||
"postprocessors": [
|
"key": "FFmpegExtractAudio",
|
||||||
{
|
"preferredcodec": "mp3",
|
||||||
"key": "FFmpegExtractAudio",
|
"preferredquality": DEFAULT_TIKTOK_AUDIO_QUALITY,
|
||||||
"preferredcodec": "mp3",
|
}],
|
||||||
"preferredquality": "192",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"quiet": True,
|
"quiet": True,
|
||||||
"no_warnings": True,
|
"no_warnings": True,
|
||||||
"extractor_args": {
|
"extractor_args": {"tiktok": {"skip_impersonation": True}},
|
||||||
"tiktok": {"skip_impersonation": True},
|
"concurrent_fragment_downloads": 4,
|
||||||
},
|
"http_chunk_size": 10485760,
|
||||||
|
}
|
||||||
|
|
||||||
|
with yt_dlp.YoutubeDL(audio_opts) as ydl:
|
||||||
|
ydl.download([url])
|
||||||
|
|
||||||
|
if not os.path.exists(audio_path):
|
||||||
|
raise Exception("TikTok audio file not created")
|
||||||
|
|
||||||
|
return video_path, audio_path
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- Instagram: Видео в 1080p (автоматически) --------------
|
||||||
|
|
||||||
|
def download_instagram_video(
|
||||||
|
url: str,
|
||||||
|
out_path: str,
|
||||||
|
cookies: str | None,
|
||||||
|
cancel_event: threading.Event,
|
||||||
|
progress_cb,
|
||||||
|
):
|
||||||
|
"""Instagram Reels: только видео в 1080p"""
|
||||||
|
ydl_opts = {
|
||||||
|
"format": f"bestvideo[height<={DEFAULT_INSTAGRAM_VIDEO_QUALITY}]+bestaudio/best",
|
||||||
|
"outtmpl": out_path.replace('.mp4', ''),
|
||||||
|
"merge_output_format": "mp4",
|
||||||
|
"cookiefile": cookies,
|
||||||
|
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"extractor_args": {"instagram": {"skip_impersonation": True}},
|
||||||
"concurrent_fragment_downloads": 4,
|
"concurrent_fragment_downloads": 4,
|
||||||
"http_chunk_size": 10485760,
|
"http_chunk_size": 10485760,
|
||||||
}
|
}
|
||||||
@@ -121,148 +148,7 @@ def download_tiktok_music(
|
|||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
|
|
||||||
# ---------------- ADD METADATA TO AUDIO ----------------
|
# ---------------- AUDIO (для YouTube) --------------
|
||||||
|
|
||||||
def add_metadata_to_audio(
|
|
||||||
input_path: str,
|
|
||||||
output_path: str,
|
|
||||||
metadata: dict,
|
|
||||||
):
|
|
||||||
"""Добавляет метаданные к аудио файлу"""
|
|
||||||
try:
|
|
||||||
# Проверяем metadata
|
|
||||||
if metadata is None:
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
if not input_path.lower().endswith('.mp3'):
|
|
||||||
shutil.copy2(input_path, output_path)
|
|
||||||
return
|
|
||||||
|
|
||||||
metadata_args = []
|
|
||||||
|
|
||||||
# Добавляем метаданные
|
|
||||||
if metadata.get('title'):
|
|
||||||
metadata_args.extend(['-metadata', f'title={metadata["title"][:100]}'])
|
|
||||||
|
|
||||||
if metadata.get('artist'):
|
|
||||||
metadata_args.extend(['-metadata', f'artist={metadata["artist"][:100]}'])
|
|
||||||
|
|
||||||
if metadata.get('album'):
|
|
||||||
metadata_args.extend(['-metadata', f'album={metadata["album"][:100]}'])
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
'ffmpeg',
|
|
||||||
'-i', input_path,
|
|
||||||
'-c', 'copy',
|
|
||||||
'-id3v2_version', '3',
|
|
||||||
'-loglevel', 'error',
|
|
||||||
'-y',
|
|
||||||
*metadata_args,
|
|
||||||
output_path
|
|
||||||
]
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=30
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
shutil.copy2(input_path, output_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Metadata error: {e}")
|
|
||||||
shutil.copy2(input_path, output_path)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- PLAYLIST DOWNLOAD ----------------
|
|
||||||
|
|
||||||
def download_playlist_videos(
|
|
||||||
playlist_info: dict,
|
|
||||||
output_dir: str,
|
|
||||||
cookies: str | None,
|
|
||||||
cancel_event: threading.Event,
|
|
||||||
progress_cb,
|
|
||||||
) -> List[str]:
|
|
||||||
"""Скачивает все видео из плейлиста"""
|
|
||||||
downloaded_files = []
|
|
||||||
|
|
||||||
ydl_opts = {
|
|
||||||
"format": "best[height<=1080]/best",
|
|
||||||
"outtmpl": os.path.join(output_dir, "%(title)s [%(id)s].%(ext)s"),
|
|
||||||
"merge_output_format": "mp4",
|
|
||||||
"cookiefile": cookies,
|
|
||||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
|
||||||
"quiet": True,
|
|
||||||
"no_warnings": True,
|
|
||||||
"ignoreerrors": True,
|
|
||||||
"extract_flat": False,
|
|
||||||
"nooverwrites": True,
|
|
||||||
"concurrent_fragment_downloads": 4,
|
|
||||||
"http_chunk_size": 10485760,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
||||||
entries = playlist_info.get('entries', [])
|
|
||||||
total_videos = len(entries)
|
|
||||||
|
|
||||||
logger.info(f"Starting playlist download with {total_videos} videos")
|
|
||||||
|
|
||||||
for i, entry in enumerate(entries, 1):
|
|
||||||
if cancel_event.is_set():
|
|
||||||
raise DownloadCancelled("Cancelled by user")
|
|
||||||
|
|
||||||
if not entry.get('url'):
|
|
||||||
logger.warning(f"Entry {i} has no URL, skipping")
|
|
||||||
continue
|
|
||||||
|
|
||||||
video_url = entry['url']
|
|
||||||
video_title = entry.get('title', f'Video {i}')
|
|
||||||
|
|
||||||
logger.info(f"Downloading video {i}/{total_videos}: {video_title}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Загружаем видео
|
|
||||||
info = ydl.extract_info(video_url, download=True)
|
|
||||||
|
|
||||||
if not info:
|
|
||||||
logger.error(f"Failed to extract info for video {i}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Получаем имя фактически созданного файла
|
|
||||||
filename = ydl.prepare_filename(info)
|
|
||||||
|
|
||||||
# Проверяем существование файла
|
|
||||||
if os.path.exists(filename):
|
|
||||||
downloaded_files.append(filename)
|
|
||||||
logger.info(f"Successfully downloaded: {os.path.basename(filename)}")
|
|
||||||
else:
|
|
||||||
# Ищем файл с другим расширением
|
|
||||||
base_name = filename.rsplit('.', 1)[0]
|
|
||||||
for ext in ['.mp4', '.mkv', '.webm', '.flv']:
|
|
||||||
alt_path = base_name + ext
|
|
||||||
if os.path.exists(alt_path):
|
|
||||||
downloaded_files.append(alt_path)
|
|
||||||
logger.info(f"Found file with extension {ext}: {os.path.basename(alt_path)}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.error(f"File not found for video {i}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error downloading video {i} ({video_title}): {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
logger.info(f"Playlist download complete. Downloaded {len(downloaded_files)} files")
|
|
||||||
return downloaded_files
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error downloading playlist: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- STANDARD AUDIO ----------------
|
|
||||||
|
|
||||||
def download_audio(
|
def download_audio(
|
||||||
url: str,
|
url: str,
|
||||||
@@ -294,4 +180,158 @@ def download_audio(
|
|||||||
}
|
}
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
ydl.download([url])
|
ydl.download([url])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- ORIGINAL QUALITY (YouTube) --------------
|
||||||
|
|
||||||
|
def download_original_quality(
|
||||||
|
url: str,
|
||||||
|
out_path: str,
|
||||||
|
cookies: str | None,
|
||||||
|
cancel_event: threading.Event,
|
||||||
|
progress_cb,
|
||||||
|
):
|
||||||
|
"""Скачивает видео в оригинальном качестве (YouTube)"""
|
||||||
|
ydl_opts = {
|
||||||
|
"format": "best",
|
||||||
|
"outtmpl": out_path,
|
||||||
|
"merge_output_format": "mp4",
|
||||||
|
"cookiefile": cookies,
|
||||||
|
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"extractor_args": {
|
||||||
|
"tiktok": {"skip_impersonation": True},
|
||||||
|
"instagram": {"skip_impersonation": True},
|
||||||
|
},
|
||||||
|
"format_sort": ["quality", "res", "codec", "size"],
|
||||||
|
"concurrent_fragment_downloads": 4,
|
||||||
|
"http_chunk_size": 10485760,
|
||||||
|
}
|
||||||
|
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
ydl.download([url])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- PLAYLIST (осталось без изменений) --------------
|
||||||
|
|
||||||
|
def download_playlist_videos(
|
||||||
|
playlist_info: dict,
|
||||||
|
output_dir: str,
|
||||||
|
cookies: str | None,
|
||||||
|
cancel_event: threading.Event,
|
||||||
|
progress_cb,
|
||||||
|
) -> List[str]:
|
||||||
|
downloaded_files = []
|
||||||
|
|
||||||
|
ydl_opts = {
|
||||||
|
"format": "best[height<=1080]/best",
|
||||||
|
"outtmpl": os.path.join(output_dir, "%(title)s [%(id)s].%(ext)s"),
|
||||||
|
"merge_output_format": "mp4",
|
||||||
|
"cookiefile": cookies,
|
||||||
|
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"ignoreerrors": True,
|
||||||
|
"extract_flat": False,
|
||||||
|
"nooverwrites": True,
|
||||||
|
"concurrent_fragment_downloads": 4,
|
||||||
|
"http_chunk_size": 10485760,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
entries = playlist_info.get('entries', [])
|
||||||
|
total_videos = len(entries)
|
||||||
|
|
||||||
|
logger.info(f"Starting playlist download with {total_videos} videos")
|
||||||
|
|
||||||
|
for i, entry in enumerate(entries, 1):
|
||||||
|
if cancel_event.is_set():
|
||||||
|
raise DownloadCancelled("Cancelled by user")
|
||||||
|
|
||||||
|
if not entry.get('url'):
|
||||||
|
logger.warning(f"Entry {i} has no URL, skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
video_url = entry['url']
|
||||||
|
video_title = entry.get('title', f'Video {i}')
|
||||||
|
|
||||||
|
logger.info(f"Downloading video {i}/{total_videos}: {video_title}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = ydl.extract_info(video_url, download=True)
|
||||||
|
|
||||||
|
if not info:
|
||||||
|
logger.error(f"Failed to extract info for video {i}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
filename = ydl.prepare_filename(info)
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
downloaded_files.append(filename)
|
||||||
|
else:
|
||||||
|
base_name = filename.rsplit('.', 1)[0]
|
||||||
|
for ext in ['.mp4', '.mkv', '.webm', '.flv']:
|
||||||
|
alt_path = base_name + ext
|
||||||
|
if os.path.exists(alt_path):
|
||||||
|
downloaded_files.append(alt_path)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.error(f"File not found for video {i}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading video {i} ({video_title}): {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Playlist download complete. Downloaded {len(downloaded_files)} files")
|
||||||
|
return downloaded_files
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading playlist: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- METADATA (осталось без изменений) --------------
|
||||||
|
|
||||||
|
def add_metadata_to_audio(
|
||||||
|
input_path: str,
|
||||||
|
output_path: str,
|
||||||
|
metadata: dict,
|
||||||
|
):
|
||||||
|
if metadata is None:
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
if not input_path.lower().endswith('.mp3'):
|
||||||
|
shutil.copy2(input_path, output_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
metadata_args = []
|
||||||
|
if metadata.get('title'):
|
||||||
|
metadata_args.extend(['-metadata', f'title={metadata["title"][:100]}'])
|
||||||
|
if metadata.get('artist'):
|
||||||
|
metadata_args.extend(['-metadata', f'artist={metadata["artist"][:100]}'])
|
||||||
|
if metadata.get('album'):
|
||||||
|
metadata_args.extend(['-metadata', f'album={metadata["album"][:100]}'])
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-i', input_path,
|
||||||
|
'-c', 'copy',
|
||||||
|
'-id3v2_version', '3',
|
||||||
|
'-loglevel', 'error',
|
||||||
|
'-y',
|
||||||
|
*metadata_args,
|
||||||
|
output_path
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
shutil.copy2(input_path, output_path)
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
|
# keyboards.py
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
|
||||||
def quality_keyboard():
|
def youtube_quality_keyboard():
|
||||||
"""Клавиатура выбора качества"""
|
"""YouTube/VK: выбор качества"""
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(text="📹 480p", callback_data="q:480"),
|
InlineKeyboardButton(text="📹 480p", callback_data="q:480"),
|
||||||
InlineKeyboardButton(text="📹 720p", callback_data="q:720"),
|
InlineKeyboardButton(text="📹 720p", callback_data="q:720"),
|
||||||
|
],
|
||||||
|
[
|
||||||
InlineKeyboardButton(text="🎬 1080p", callback_data="q:1080"),
|
InlineKeyboardButton(text="🎬 1080p", callback_data="q:1080"),
|
||||||
InlineKeyboardButton(text="🎥 1440p", callback_data="q:1440"),
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(text="🎧 Аудио", callback_data="audio"),
|
InlineKeyboardButton(text="🎧 Аудио", callback_data="audio"),
|
||||||
@@ -19,7 +21,7 @@ def quality_keyboard():
|
|||||||
|
|
||||||
|
|
||||||
def cancel_keyboard():
|
def cancel_keyboard():
|
||||||
"""Клавиатура с кнопкой отмены"""
|
"""Клавиатура с кнопкой отмены (для плейлистов)"""
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[InlineKeyboardButton(text="⛔ Отменить загрузку", callback_data="cancel")]
|
[InlineKeyboardButton(text="⛔ Отменить загрузку", callback_data="cancel")]
|
||||||
]
|
]
|
||||||
@@ -27,7 +29,7 @@ def cancel_keyboard():
|
|||||||
|
|
||||||
|
|
||||||
def playlist_keyboard(confirm=False):
|
def playlist_keyboard(confirm=False):
|
||||||
"""Клавиатура для плейлистов"""
|
"""Клавиатура для плейлистов YouTube"""
|
||||||
if confirm:
|
if confirm:
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[
|
[
|
||||||
@@ -45,45 +47,4 @@ def playlist_keyboard(confirm=False):
|
|||||||
InlineKeyboardButton(text="❌ Отмена", callback_data="cancel"),
|
InlineKeyboardButton(text="❌ Отмена", callback_data="cancel"),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
|
||||||
|
|
||||||
|
|
||||||
def platform_keyboard(platform):
|
|
||||||
"""Клавиатура для Instagram"""
|
|
||||||
keyboard = [
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text=f"🎬 Оригинальное качество", callback_data="original_quality"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="📹 480p", callback_data="q:480"),
|
|
||||||
InlineKeyboardButton(text="📹 720p", callback_data="q:720"),
|
|
||||||
InlineKeyboardButton(text="🎬 1080p", callback_data="q:1080"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="🎧 Аудио", callback_data="audio"),
|
|
||||||
InlineKeyboardButton(text="❌ Отмена", callback_data="cancel"),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
|
||||||
|
|
||||||
|
|
||||||
def tiktok_keyboard():
|
|
||||||
"""Специальная клавиатура для TikTok"""
|
|
||||||
keyboard = [
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="🎬 Оригинальное качество", callback_data="original_quality"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="📹 480p", callback_data="q:480"),
|
|
||||||
InlineKeyboardButton(text="📹 720p", callback_data="q:720"),
|
|
||||||
InlineKeyboardButton(text="🎬 1080p", callback_data="q:1080"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="🎧 Аудио из видео", callback_data="audio"),
|
|
||||||
InlineKeyboardButton(text="🎵 Только звук (TikTok)", callback_data="tiktok_music"),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text="❌ Отмена", callback_data="cancel"),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
return InlineKeyboardMarkup(inline_keyboard=keyboard)
|
||||||
976
bot/main.py
976
bot/main.py
File diff suppressed because it is too large
Load Diff
11
wireguard/wg_confs/wg0.conf
Normal file
11
wireguard/wg_confs/wg0.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Interface]
|
||||||
|
Address = 10.7.0.13/24
|
||||||
|
DNS = 1.0.0.1, 1.1.1.1, 1.0.0.1, 1.1.1.1
|
||||||
|
PrivateKey = 8GTpMYtpOhWDp6AKXxukwFRsiwvERf2UVCjCrdB+M10=
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = ekvrE4SMgydQhkWdWgJazlrILSEAlRbwCI4KcXUQv3A=
|
||||||
|
PresharedKey = eaCOqeiYdfso4v7/ucrvFzB1MtDnXy1yTiujL08jJIE=
|
||||||
|
AllowedIPs = 0.0.0.0/0, ::/0
|
||||||
|
Endpoint = 109.107.170.183:51821
|
||||||
|
PersistentKeepalive = 25
|
||||||
Reference in New Issue
Block a user