Merge pull request 'update_0.2' (#1) from update_0.2 into main
Reviewed-on: http://10.0.1.48:3010/smolkik_adm/downloads-all-bot/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
@@ -1,17 +1,13 @@
|
||||
# cache.py
|
||||
import hashlib
|
||||
|
||||
import os
|
||||
|
||||
def cache_key(url: str, quality: str, audio: bool = False) -> str:
|
||||
"""Генерирует ключ кэша на основе URL, качества и типа"""
|
||||
key_str = f"{url}_{quality}_{audio}"
|
||||
return hashlib.sha256(key_str.encode()).hexdigest()
|
||||
|
||||
|
||||
def cache_path(cache_dir: str, key: str, extension: str) -> str:
|
||||
"""Создает путь к файлу в кэше"""
|
||||
# Создаем вложенную структуру для лучшей организации
|
||||
subdir = key[:2]
|
||||
import os
|
||||
full_dir = os.path.join(cache_dir, subdir)
|
||||
os.makedirs(full_dir, exist_ok=True)
|
||||
return os.path.join(full_dir, f"{key}.{extension}")
|
||||
@@ -1,3 +1,4 @@
|
||||
# config.py
|
||||
import os
|
||||
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))
|
||||
RATE_LIMIT_SECONDS = int(os.getenv("RATE_LIMIT_SECONDS", 20))
|
||||
MAX_FILE_SIZE_MB = 2000
|
||||
CACHE_MAX_AGE_DAYS = 7 # Удалять файлы старше 7 дней
|
||||
CACHE_MAX_SIZE_MB = 4096 # Максимальный размер кэша 1 ГБ (0 = без ограничения)
|
||||
CACHE_MAX_AGE_DAYS = 7
|
||||
CACHE_MAX_SIZE_MB = 4096
|
||||
|
||||
# Авто-качества для TikTok и Instagram (без выбора)
|
||||
DEFAULT_TIKTOK_VIDEO_QUALITY = "1080"
|
||||
DEFAULT_TIKTOK_AUDIO_QUALITY = "192" # кбит/с
|
||||
DEFAULT_INSTAGRAM_VIDEO_QUALITY = "1080"
|
||||
|
||||
# Private mode
|
||||
ALLOWED_USERS = set(
|
||||
int(uid.strip())
|
||||
|
||||
@@ -1,32 +1,60 @@
|
||||
# downloader.py
|
||||
import yt_dlp
|
||||
import threading
|
||||
import os
|
||||
import logging
|
||||
import subprocess
|
||||
import json
|
||||
import shutil
|
||||
from typing import List, Optional
|
||||
import asyncio
|
||||
from typing import List
|
||||
|
||||
from config import (
|
||||
DEFAULT_TIKTOK_VIDEO_QUALITY,
|
||||
DEFAULT_TIKTOK_AUDIO_QUALITY,
|
||||
DEFAULT_INSTAGRAM_VIDEO_QUALITY,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DownloadCancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _progress_hook(cancel_event, progress_cb):
|
||||
def hook(d):
|
||||
if cancel_event.is_set():
|
||||
raise DownloadCancelled("Cancelled by user")
|
||||
|
||||
if d["status"] in ["downloading", "finished"] and progress_cb:
|
||||
progress_cb(d)
|
||||
|
||||
return hook
|
||||
|
||||
def optimize_for_telegram(input_path: str, output_path: str) -> None:
|
||||
"""
|
||||
Синхронная функция: конвертирует видео в Telegram-safe mp4
|
||||
"""
|
||||
file_size_mb = os.path.getsize(input_path) / (1024 * 1024)
|
||||
crf = 28 if file_size_mb > 50 else 23
|
||||
|
||||
# ---------------- STANDARD VIDEO ----------------
|
||||
cmd = [
|
||||
'ffmpeg',
|
||||
'-i', input_path,
|
||||
'-c:v', 'libx264',
|
||||
'-preset', 'fast',
|
||||
'-crf', str(crf),
|
||||
'-c:a', 'aac',
|
||||
'-b:a', '128k',
|
||||
'-movflags', '+faststart',
|
||||
'-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
|
||||
'-y',
|
||||
output_path
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
||||
if result.returncode != 0:
|
||||
logger.error(f"FFmpeg error: {result.stderr}")
|
||||
shutil.copy2(input_path, output_path)
|
||||
|
||||
|
||||
# ---------------- YOUTUBE/VK: Video (с выбором качества) ------
|
||||
def download_video(
|
||||
url: str,
|
||||
quality: str,
|
||||
@@ -38,15 +66,10 @@ def download_video(
|
||||
ydl_opts = {
|
||||
"format": f"best[height<={quality}]/best",
|
||||
"outtmpl": out_path,
|
||||
"merge_output_format": "mp4",
|
||||
"cookiefile": cookies,
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
"extractor_args": {
|
||||
"tiktok": {"skip_impersonation": True},
|
||||
"instagram": {"skip_impersonation": True},
|
||||
},
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
@@ -54,65 +77,48 @@ def download_video(
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
# Ищем реальный файл
|
||||
base = out_path.rsplit('.', 1)[0]
|
||||
actual_video = None
|
||||
for ext in ['', '.mp4', '.mov', '.webm', '.mkv']:
|
||||
candidate = base + ext
|
||||
if os.path.exists(candidate):
|
||||
actual_video = candidate
|
||||
break
|
||||
|
||||
# ---------------- ORIGINAL QUALITY ----------------
|
||||
if not actual_video:
|
||||
raise Exception(f"Video file not created for {url}")
|
||||
|
||||
def download_original_quality(
|
||||
# Конвертируем в Telegram-safe mp4 (синхронно)
|
||||
optimized_path = base + "_telegram.mp4"
|
||||
optimize_for_telegram(actual_video, optimized_path) # ← без await!
|
||||
|
||||
# Удаляем старый файл и ставим конвертированный
|
||||
for path in [out_path, actual_video]:
|
||||
if os.path.exists(path): os.remove(path)
|
||||
os.rename(optimized_path, out_path)
|
||||
|
||||
|
||||
# ---------------- AUDIO (YouTube/VK) ------
|
||||
def download_audio(
|
||||
url: str,
|
||||
out_path: str,
|
||||
cookies: str | None,
|
||||
cancel_event: threading.Event,
|
||||
progress_cb,
|
||||
):
|
||||
"""Скачивает видео в оригинальном качестве"""
|
||||
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])
|
||||
|
||||
|
||||
# ---------------- TIKTOK MUSIC ONLY ----------------
|
||||
|
||||
def download_tiktok_music(
|
||||
url: str,
|
||||
out_path: str,
|
||||
cookies: str | None,
|
||||
cancel_event: threading.Event,
|
||||
progress_cb,
|
||||
):
|
||||
"""Скачивает только звук из TikTok"""
|
||||
ydl_opts = {
|
||||
"format": "bestaudio/best",
|
||||
"outtmpl": out_path.replace('.mp3', ''),
|
||||
"cookiefile": cookies,
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": "192",
|
||||
},
|
||||
],
|
||||
"postprocessors": [{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": "192",
|
||||
}],
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
"extractor_args": {
|
||||
"tiktok": {"skip_impersonation": True},
|
||||
},
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
@@ -120,64 +126,158 @@ def download_tiktok_music(
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
# Ищем файл
|
||||
base = out_path.rsplit('.', 1)[0]
|
||||
actual_audio = None
|
||||
for ext in ['', '.mp3', '.m4a', '.opus', '.weba']:
|
||||
candidate = base + ext
|
||||
if os.path.exists(candidate):
|
||||
actual_audio = candidate
|
||||
break
|
||||
|
||||
# ---------------- ADD METADATA TO AUDIO ----------------
|
||||
if not actual_audio:
|
||||
raise Exception(f"Audio file not created for {url}")
|
||||
|
||||
def add_metadata_to_audio(
|
||||
input_path: str,
|
||||
output_path: str,
|
||||
metadata: dict,
|
||||
# Конвертируем в mp3
|
||||
if actual_audio != out_path:
|
||||
optimized = base + "_telegram.mp3"
|
||||
optimize_for_telegram_audio(actual_audio, optimized) # ← отдельная функция
|
||||
if os.path.exists(out_path): os.remove(out_path)
|
||||
os.rename(optimized, out_path)
|
||||
|
||||
|
||||
# ---------------- TikTok: Video + Audio (автоматически) ------
|
||||
def download_tiktok_video_and_audio(
|
||||
url: str,
|
||||
video_path: str,
|
||||
audio_path: str,
|
||||
cookies: str | None,
|
||||
cancel_event: threading.Event,
|
||||
progress_cb,
|
||||
):
|
||||
"""Добавляет метаданные к аудио файлу"""
|
||||
try:
|
||||
# Проверяем metadata
|
||||
if metadata is None:
|
||||
metadata = {}
|
||||
# Скачиваем видео
|
||||
video_opts = {
|
||||
"format": f"bestvideo[height<={DEFAULT_TIKTOK_VIDEO_QUALITY}]+bestaudio/best",
|
||||
"outtmpl": video_path,
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"quiet": True,
|
||||
"no_warnings": False,
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
|
||||
if not input_path.lower().endswith('.mp3'):
|
||||
shutil.copy2(input_path, output_path)
|
||||
return
|
||||
with yt_dlp.YoutubeDL(video_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
metadata_args = []
|
||||
# Ищем видеофайл
|
||||
base_video = video_path.rsplit('.', 1)[0]
|
||||
actual_video = None
|
||||
for ext in ['', '.mp4', '.mov', '.webm']:
|
||||
candidate = base_video + ext
|
||||
if os.path.exists(candidate):
|
||||
actual_video = candidate
|
||||
break
|
||||
if not actual_video:
|
||||
raise Exception("TikTok video file not created")
|
||||
|
||||
# Добавляем метаданные
|
||||
if metadata.get('title'):
|
||||
metadata_args.extend(['-metadata', f'title={metadata["title"][:100]}'])
|
||||
# Конвертируем в Telegram-safe mp4
|
||||
optimized_video = base_video + "_telegram.mp4"
|
||||
optimize_for_telegram(actual_video, optimized_video)
|
||||
for path in [video_path, actual_video]:
|
||||
if os.path.exists(path): os.remove(path)
|
||||
os.rename(optimized_video, video_path)
|
||||
|
||||
if metadata.get('artist'):
|
||||
metadata_args.extend(['-metadata', f'artist={metadata["artist"][:100]}'])
|
||||
# Аудио
|
||||
audio_opts = {
|
||||
"format": "bestaudio/best",
|
||||
"outtmpl": audio_path.replace('.mp3', ''),
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"postprocessors": [{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": DEFAULT_TIKTOK_AUDIO_QUALITY,
|
||||
}],
|
||||
"quiet": True,
|
||||
"no_warnings": False,
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
|
||||
if metadata.get('album'):
|
||||
metadata_args.extend(['-metadata', f'album={metadata["album"][:100]}'])
|
||||
with yt_dlp.YoutubeDL(audio_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
cmd = [
|
||||
'ffmpeg',
|
||||
'-i', input_path,
|
||||
'-c', 'copy',
|
||||
'-id3v2_version', '3',
|
||||
'-loglevel', 'error',
|
||||
'-y',
|
||||
*metadata_args,
|
||||
output_path
|
||||
]
|
||||
base_audio = audio_path.rsplit('.', 1)[0]
|
||||
actual_audio = None
|
||||
for ext in ['', '.mp3', '.m4a', '.opus']:
|
||||
candidate = base_audio + ext
|
||||
if os.path.exists(candidate):
|
||||
actual_audio = candidate
|
||||
break
|
||||
if not actual_audio:
|
||||
raise Exception("TikTok audio file not created")
|
||||
|
||||
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)
|
||||
if actual_audio != audio_path:
|
||||
optimized_audio = base_audio + "_telegram.mp3"
|
||||
optimize_for_telegram_audio(actual_audio, optimized_audio)
|
||||
if os.path.exists(audio_path): os.remove(audio_path)
|
||||
os.rename(optimized_audio, audio_path)
|
||||
|
||||
|
||||
# ---------------- PLAYLIST DOWNLOAD ----------------
|
||||
# ---------------- Instagram: Video (1080p) ------
|
||||
def download_instagram_video(
|
||||
url: str,
|
||||
out_path: str,
|
||||
cookies: str | None,
|
||||
cancel_event: threading.Event,
|
||||
progress_cb,
|
||||
):
|
||||
ydl_opts = {
|
||||
"format": f"bestvideo[height<={DEFAULT_INSTAGRAM_VIDEO_QUALITY}]+bestaudio/best",
|
||||
"outtmpl": out_path,
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"quiet": True,
|
||||
"no_warnings": False,
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
base = out_path.rsplit('.', 1)[0]
|
||||
actual = None
|
||||
for ext in ['', '.mp4', '.mov', '.webm']:
|
||||
candidate = base + ext
|
||||
if os.path.exists(candidate):
|
||||
actual = candidate
|
||||
break
|
||||
if not actual:
|
||||
raise Exception("Instagram video not created")
|
||||
|
||||
optimized = base + "_telegram.mp4"
|
||||
optimize_for_telegram(actual, optimized)
|
||||
if os.path.exists(out_path): os.remove(out_path)
|
||||
os.rename(optimized, out_path)
|
||||
|
||||
|
||||
# ---------------- Audio helper (синхронная) ------
|
||||
def optimize_for_telegram_audio(input_path: str, output_path: str) -> None:
|
||||
"""Конвертирует аудио в Telegram-safe mp3"""
|
||||
cmd = [
|
||||
'ffmpeg',
|
||||
'-i', input_path,
|
||||
'-c:a', 'aac',
|
||||
'-b:a', '192k',
|
||||
'-loglevel', 'error',
|
||||
'-y',
|
||||
output_path
|
||||
]
|
||||
subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
||||
|
||||
|
||||
# ---------------- Playlist ------
|
||||
def download_playlist_videos(
|
||||
playlist_info: dict,
|
||||
output_dir: str,
|
||||
@@ -185,14 +285,11 @@ def download_playlist_videos(
|
||||
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,
|
||||
"cookiefile": cookies if cookies and os.path.exists(cookies) else None,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
@@ -206,92 +303,20 @@ def download_playlist_videos(
|
||||
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}")
|
||||
|
||||
info = ydl.extract_info(entry['url'], download=True)
|
||||
if info:
|
||||
filename = ydl.prepare_filename(info)
|
||||
if os.path.exists(filename):
|
||||
downloaded_files.append(filename)
|
||||
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")
|
||||
logger.error(f"Error downloading video {i}: {e}")
|
||||
return downloaded_files
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading playlist: {e}")
|
||||
logger.error(f"Playlist download error: {e}")
|
||||
raise
|
||||
|
||||
|
||||
# ---------------- STANDARD AUDIO ----------------
|
||||
|
||||
def download_audio(
|
||||
url: str,
|
||||
out_path: str,
|
||||
cookies: str | None,
|
||||
cancel_event: threading.Event,
|
||||
progress_cb,
|
||||
):
|
||||
ydl_opts = {
|
||||
"format": "bestaudio/best",
|
||||
"outtmpl": out_path.replace('.mp3', ''),
|
||||
"cookiefile": cookies,
|
||||
"progress_hooks": [_progress_hook(cancel_event, progress_cb)],
|
||||
"postprocessors": [
|
||||
{
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
"preferredquality": "192",
|
||||
},
|
||||
],
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
"extractor_args": {
|
||||
"tiktok": {"skip_impersonation": True},
|
||||
"instagram": {"skip_impersonation": True},
|
||||
},
|
||||
"concurrent_fragment_downloads": 4,
|
||||
"http_chunk_size": 10485760,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
@@ -1,14 +1,16 @@
|
||||
# keyboards.py
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
|
||||
def quality_keyboard():
|
||||
"""Клавиатура выбора качества"""
|
||||
def youtube_quality_keyboard():
|
||||
"""YouTube/VK: выбор качества"""
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(text="📹 480p", callback_data="q:480"),
|
||||
InlineKeyboardButton(text="📹 720p", callback_data="q:720"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="🎬 1080p", callback_data="q:1080"),
|
||||
InlineKeyboardButton(text="🎥 1440p", callback_data="q:1440"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="🎧 Аудио", callback_data="audio"),
|
||||
@@ -19,7 +21,7 @@ def quality_keyboard():
|
||||
|
||||
|
||||
def cancel_keyboard():
|
||||
"""Клавиатура с кнопкой отмены"""
|
||||
"""Клавиатура с кнопкой отмены (для плейлистов)"""
|
||||
keyboard = [
|
||||
[InlineKeyboardButton(text="⛔ Отменить загрузку", callback_data="cancel")]
|
||||
]
|
||||
@@ -27,7 +29,7 @@ def cancel_keyboard():
|
||||
|
||||
|
||||
def playlist_keyboard(confirm=False):
|
||||
"""Клавиатура для плейлистов"""
|
||||
"""Клавиатура для плейлистов YouTube"""
|
||||
if confirm:
|
||||
keyboard = [
|
||||
[
|
||||
@@ -46,44 +48,3 @@ def playlist_keyboard(confirm=False):
|
||||
]
|
||||
]
|
||||
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)
|
||||
996
bot/main.py
996
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