Compare commits
2 Commits
4051b0f054
...
61d721b514
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61d721b514 | ||
|
|
2e2b10890c |
3
.env
3
.env
@@ -1,6 +1,7 @@
|
|||||||
# --- TELEGRAM BOT ---
|
# --- TELEGRAM BOT ---
|
||||||
BOT_TOKEN=8747085830:AAHsOTdZbK40daE1Lxmjvw5CcKHY_A7sucI
|
BOT_TOKEN=8747085830:AAHsOTdZbK40daE1Lxmjvw5CcKHY_A7sucI
|
||||||
AUTHORIZED_USER_ID=951506682
|
ADMIN_ID=951506682
|
||||||
|
AUTHORIZED_USER_IDS=
|
||||||
|
|
||||||
# --- POSTGRESQL ---
|
# --- POSTGRESQL ---
|
||||||
# Gunakan 'localhost' jika aplikasi jalan langsung di VPS (tanpa Docker),
|
# Gunakan 'localhost' jika aplikasi jalan langsung di VPS (tanpa Docker),
|
||||||
|
|||||||
Binary file not shown.
207
bot.py
207
bot.py
@@ -11,7 +11,7 @@ from aiogram.client.session.aiohttp import AiohttpSession
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
|
||||||
from yt_engine import process_youtube_request, get_recommendations
|
from yt_engine import process_youtube_request, get_recommendations, search_youtube_list
|
||||||
from db_manager import db
|
from db_manager import db
|
||||||
from s3_manager import upload_audio, download_audio, delete_audio
|
from s3_manager import upload_audio, download_audio, delete_audio
|
||||||
|
|
||||||
@@ -20,13 +20,24 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
||||||
AUTHORIZED_USER_ID = int(os.getenv("AUTHORIZED_USER_ID", 0))
|
|
||||||
|
# --- KONFIGURASI ADMIN & USERS ---
|
||||||
|
ADMIN_ID = int(os.getenv("ADMIN_ID", 0))
|
||||||
|
auth_users_str = os.getenv("AUTHORIZED_USER_IDS", "")
|
||||||
|
AUTHORIZED_USER_IDS = {int(uid.strip()) for uid in auth_users_str.split(",") if uid.strip().isdigit()}
|
||||||
|
|
||||||
|
# Pastikan Admin selalu masuk whitelist
|
||||||
|
if ADMIN_ID != 0:
|
||||||
|
AUTHORIZED_USER_IDS.add(ADMIN_ID)
|
||||||
|
|
||||||
|
# Penyimpanan sementara user yang sedang menunggu persetujuan (agar admin tidak di-spam)
|
||||||
|
pending_users = set()
|
||||||
|
|
||||||
session = AiohttpSession(timeout=300)
|
session = AiohttpSession(timeout=300)
|
||||||
bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=None))
|
bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=None))
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
||||||
music_queue = asyncio.Queue()
|
music_queue = asyncio.Queue()
|
||||||
|
user_searches = {}
|
||||||
|
|
||||||
async def queue_worker():
|
async def queue_worker():
|
||||||
while True:
|
while True:
|
||||||
@@ -38,113 +49,205 @@ async def queue_worker():
|
|||||||
finally:
|
finally:
|
||||||
music_queue.task_done()
|
music_queue.task_done()
|
||||||
|
|
||||||
|
# --- SATPAM BOT DENGAN FITUR NOTIFIKASI ADMIN ---
|
||||||
class SecurityMiddleware(BaseMiddleware):
|
class SecurityMiddleware(BaseMiddleware):
|
||||||
async def __call__(self, handler, event: types.Message | CallbackQuery, data: dict):
|
async def __call__(self, handler, event: types.Message | CallbackQuery, data: dict):
|
||||||
if event.from_user.id != AUTHORIZED_USER_ID: return
|
user_id = event.from_user.id
|
||||||
|
|
||||||
|
if user_id not in AUTHORIZED_USER_IDS:
|
||||||
|
# Jika user baru dan belum ada di antrean pending
|
||||||
|
if user_id not in pending_users and ADMIN_ID != 0:
|
||||||
|
pending_users.add(user_id)
|
||||||
|
username = event.from_user.username or event.from_user.first_name or "Unknown"
|
||||||
|
|
||||||
|
# 1. Kirim pesan ke Admin dengan tombol
|
||||||
|
kb = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text="✅ Izinkan", callback_data=f"approve_{user_id}"),
|
||||||
|
InlineKeyboardButton(text="❌ Tolak", callback_data=f"reject_{user_id}")
|
||||||
|
]
|
||||||
|
])
|
||||||
|
try:
|
||||||
|
await bot.send_message(
|
||||||
|
ADMIN_ID,
|
||||||
|
f"🔔 **IZIN AKSES BARU!**\n\n👤 Nama: `{username}`\n🆔 ID: `{user_id}`\n\n_Apakah kamu mengizinkan orang ini menggunakan bot musikmu?_",
|
||||||
|
reply_markup=kb, parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Gagal mengirim notif ke admin: {e}")
|
||||||
|
|
||||||
|
# 2. Kirim pesan ke User asing
|
||||||
|
if isinstance(event, types.Message):
|
||||||
|
await event.answer("🔒 **Akses Terkunci.**\nBot ini bersifat private. Permintaan izin telah dikirim ke Admin. Harap tunggu persetujuan.", parse_mode="Markdown")
|
||||||
|
elif isinstance(event, types.CallbackQuery):
|
||||||
|
await event.answer("Akses terkunci! Permintaan izin dikirim ke Admin.", show_alert=True)
|
||||||
|
|
||||||
|
# Abaikan semua perintah dari user asing ini
|
||||||
|
return
|
||||||
|
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
dp.message.middleware(SecurityMiddleware())
|
dp.message.middleware(SecurityMiddleware())
|
||||||
dp.callback_query.middleware(SecurityMiddleware())
|
dp.callback_query.middleware(SecurityMiddleware())
|
||||||
|
|
||||||
# Ekstraktor ID YouTube
|
# --- FUNGSI UPDATE .ENV OTOMATIS ---
|
||||||
|
def add_user_to_env(new_id):
|
||||||
|
try:
|
||||||
|
with open(".env", "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
with open(".env", "w") as f:
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("AUTHORIZED_USER_IDS="):
|
||||||
|
clean_line = line.strip().rstrip(',')
|
||||||
|
line = f"{clean_line},{new_id}\n"
|
||||||
|
f.write(line)
|
||||||
|
logger.info(f"User {new_id} berhasil ditambahkan permanen ke .env")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Gagal update .env: {e}")
|
||||||
|
|
||||||
|
# --- HANDLER TOMBOL ADMIN (APPROVE / REJECT) ---
|
||||||
|
@dp.callback_query(F.data.startswith("approve_") | F.data.startswith("reject_"))
|
||||||
|
async def handle_admin_action(callback: CallbackQuery):
|
||||||
|
if callback.from_user.id != ADMIN_ID:
|
||||||
|
return await callback.answer("Kamu bukan Admin!", show_alert=True)
|
||||||
|
|
||||||
|
action, target_id_str = callback.data.split("_")
|
||||||
|
target_id = int(target_id_str)
|
||||||
|
|
||||||
|
if target_id in pending_users:
|
||||||
|
pending_users.remove(target_id)
|
||||||
|
|
||||||
|
await callback.message.edit_reply_markup(reply_markup=None)
|
||||||
|
|
||||||
|
if action == "approve":
|
||||||
|
AUTHORIZED_USER_IDS.add(target_id)
|
||||||
|
add_user_to_env(target_id)
|
||||||
|
await callback.message.edit_text(f"✅ User `{target_id}` telah **DISETUJUI** dan ditambahkan permanen ke sistem.", parse_mode="Markdown")
|
||||||
|
try:
|
||||||
|
await bot.send_message(target_id, "🎉 **AKSES DISETUJUI!**\nAdmin telah memberikan izin. Silakan ketik /start atau /play <judul> untuk mulai mencari lagu.", parse_mode="Markdown")
|
||||||
|
except: pass
|
||||||
|
else:
|
||||||
|
await callback.message.edit_text(f"❌ User `{target_id}` telah **DITOLAK**.", parse_mode="Markdown")
|
||||||
|
try:
|
||||||
|
await bot.send_message(target_id, "❌ **AKSES DITOLAK.**\nAdmin tidak memberikan izin untuk menggunakan bot ini.", parse_mode="Markdown")
|
||||||
|
except: pass
|
||||||
|
|
||||||
def extract_video_id(query):
|
def extract_video_id(query):
|
||||||
match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", query)
|
match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", query)
|
||||||
return match.group(1) if match else None
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
def get_search_keyboard(chat_id, page=0):
|
||||||
|
data = user_searches.get(chat_id)
|
||||||
|
if not data: return None
|
||||||
|
results = data['results']
|
||||||
|
start_idx = page * 10
|
||||||
|
end_idx = start_idx + 10
|
||||||
|
page_results = results[start_idx:end_idx]
|
||||||
|
|
||||||
|
kb = []
|
||||||
|
for rec in page_results:
|
||||||
|
display_text = f"🎵 {rec['title']} | {rec.get('uploader', 'YT')}"
|
||||||
|
short_text = display_text[:55] + "..." if len(display_text) > 55 else display_text
|
||||||
|
kb.append([InlineKeyboardButton(text=short_text, callback_data=f"play_{rec['id']}")])
|
||||||
|
|
||||||
|
nav_buttons = []
|
||||||
|
if page > 0: nav_buttons.append(InlineKeyboardButton(text="⬅️ Prev", callback_data=f"page_{page-1}"))
|
||||||
|
if end_idx < len(results): nav_buttons.append(InlineKeyboardButton(text="Next ➡️", callback_data=f"page_{page+1}"))
|
||||||
|
if nav_buttons: kb.append(nav_buttons)
|
||||||
|
|
||||||
|
return InlineKeyboardMarkup(inline_keyboard=kb)
|
||||||
|
|
||||||
@dp.message(Command("start"))
|
@dp.message(Command("start"))
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
await message.answer("🎧 **Music Bot Ready (M4A Turbo)!**\nKirim format: `/play <judul lagu>`\nSetelah lagu terkirim, klik rekomendasi di bawahnya.", parse_mode="Markdown")
|
await message.answer("🎧 **Music Bot VIP Ready!**\nKirim format: `/play <judul lagu>`", parse_mode="Markdown")
|
||||||
|
|
||||||
@dp.message(Command("play"))
|
@dp.message(Command("play"))
|
||||||
async def cmd_play(message: types.Message):
|
async def cmd_play(message: types.Message):
|
||||||
query = message.text.replace("/play", "").strip()
|
query = message.text.replace("/play", "").strip()
|
||||||
if not query: return await message.answer("⚠️ Masukkan judul lagu.")
|
if not query: return await message.answer("⚠️ Masukkan judul lagu.")
|
||||||
status_msg = await message.answer("⏳ Memproses pesananmu...")
|
|
||||||
await music_queue.put((query, message.chat.id, status_msg.message_id))
|
status_msg = await message.answer("🔍 *Mencari daftar lagu...*", parse_mode="Markdown")
|
||||||
|
results = await search_youtube_list(query, max_results=30)
|
||||||
|
|
||||||
|
if not results: return await status_msg.edit_text("❌ Lagu tidak ditemukan.")
|
||||||
|
|
||||||
|
user_searches[message.chat.id] = {
|
||||||
|
'header': f"🔎 Hasil pencarian: **{query}**",
|
||||||
|
'results': results
|
||||||
|
}
|
||||||
|
|
||||||
|
kb = get_search_keyboard(message.chat.id, page=0)
|
||||||
|
await status_msg.edit_text(text=f"{user_searches[message.chat.id]['header']}\n_Halaman 1_", reply_markup=kb, parse_mode="Markdown")
|
||||||
|
|
||||||
|
@dp.callback_query(F.data.startswith("page_"))
|
||||||
|
async def handle_page_click(callback: CallbackQuery):
|
||||||
|
chat_id = callback.message.chat.id
|
||||||
|
if chat_id not in user_searches:
|
||||||
|
return await callback.answer("Sesi kadaluarsa. Ketik /play lagi.", show_alert=True)
|
||||||
|
|
||||||
|
page = int(callback.data.split("_")[1])
|
||||||
|
kb = get_search_keyboard(chat_id, page)
|
||||||
|
|
||||||
|
if kb:
|
||||||
|
header = user_searches[chat_id]['header']
|
||||||
|
await callback.message.edit_text(text=f"{header}\n_Halaman {page+1}_", reply_markup=kb, parse_mode="Markdown")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
@dp.callback_query(F.data.startswith("play_"))
|
@dp.callback_query(F.data.startswith("play_"))
|
||||||
async def handle_suggestion_click(callback: CallbackQuery):
|
async def handle_suggestion_click(callback: CallbackQuery):
|
||||||
video_id = callback.data.split("_", 1)[1]
|
video_id = callback.data.split("_", 1)[1]
|
||||||
await callback.message.edit_reply_markup(reply_markup=None)
|
await callback.message.edit_reply_markup(reply_markup=None)
|
||||||
status_msg = await bot.send_message(callback.message.chat.id, "⏳ Memproses lagu pilihanmu...")
|
status_msg = await bot.send_message(callback.message.chat.id, "⏳ Mengunduh lagu pilihanmu...")
|
||||||
query_url = f"https://www.youtube.com/watch?v={video_id}"
|
query_url = f"https://www.youtube.com/watch?v={video_id}"
|
||||||
await music_queue.put((query_url, callback.message.chat.id, status_msg.message_id))
|
await music_queue.put((query_url, callback.message.chat.id, status_msg.message_id))
|
||||||
await callback.answer("Siapp! Diproses...")
|
await callback.answer("Siapp! Diproses...")
|
||||||
|
|
||||||
# --- CORE LOGIC (DENGAN SMART CACHE) ---
|
|
||||||
async def process_music_request(query: str, chat_id: int, status_msg_id: int):
|
async def process_music_request(query: str, chat_id: int, status_msg_id: int):
|
||||||
local_file = None
|
local_file = None
|
||||||
total_start_time = time.time()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"▶️ MEMULAI PROSES: {query}")
|
|
||||||
|
|
||||||
# 1. SMART CACHE CHECK (CEK DB DULUAN)
|
|
||||||
video_id = extract_video_id(query)
|
video_id = extract_video_id(query)
|
||||||
cached_data = None
|
cached_data = None
|
||||||
|
|
||||||
if video_id:
|
if video_id:
|
||||||
t_s3_check = time.time()
|
|
||||||
cached_data = await db.get_cache(video_id)
|
cached_data = await db.get_cache(video_id)
|
||||||
if cached_data:
|
if cached_data:
|
||||||
logger.info(f"⚡ CACHE HIT! File sudah ada di MinIO. (Cek butuh {time.time() - t_s3_check:.2f}s)")
|
title = cached_data.get('title', 'Lagu')
|
||||||
title = cached_data['title']
|
s3_object_name = cached_data.get('s3_object_key', f"{video_id}.m4a")
|
||||||
s3_object_name = cached_data['s3_object_key']
|
|
||||||
# Bisa saja file lama berekstensi mp3, atau baru berekstensi m4a
|
|
||||||
ext = s3_object_name.split('.')[-1]
|
ext = s3_object_name.split('.')[-1]
|
||||||
local_file = f"downloads/cache_{video_id}.{ext}"
|
local_file = f"downloads/cache_{video_id}.{ext}"
|
||||||
|
await bot.edit_message_text("⚡ Mengambil dari Cache...", chat_id, status_msg_id)
|
||||||
await bot.edit_message_text("⚡ Mengambil langsung dari Cache (MinIO)...", chat_id, status_msg_id)
|
|
||||||
await download_audio(s3_object_name, local_file)
|
await download_audio(s3_object_name, local_file)
|
||||||
|
|
||||||
# 2. JIKA TIDAK ADA DI CACHE, BARU PANGGIL YOUTUBE
|
|
||||||
if not cached_data:
|
if not cached_data:
|
||||||
await bot.edit_message_text("🔍 Mencari dan mengunduh dari YouTube...", chat_id, status_msg_id)
|
await bot.edit_message_text("🔍 Mengunduh dari YouTube...", chat_id, status_msg_id)
|
||||||
t_yt_start = time.time()
|
|
||||||
yt_result = await process_youtube_request(query)
|
yt_result = await process_youtube_request(query)
|
||||||
logger.info(f"⏱️ yt-dlp (Tanpa FFmpeg) memakan waktu: {time.time() - t_yt_start:.2f} detik")
|
|
||||||
|
|
||||||
if yt_result["status"] == "error":
|
if yt_result["status"] == "error":
|
||||||
return await bot.edit_message_text(f"❌ Gagal: {yt_result['message']}", chat_id, status_msg_id)
|
return await bot.edit_message_text(f"❌ Gagal: {yt_result['message']}", chat_id, status_msg_id)
|
||||||
|
|
||||||
video_id = yt_result["video_id"]
|
video_id = yt_result["video_id"]
|
||||||
title = yt_result["title"]
|
title = yt_result["title"]
|
||||||
local_file = yt_result["file_path"]
|
local_file = yt_result["file_path"]
|
||||||
|
|
||||||
# Simpan sesuai ekstensi aslinya (sekarang biasanya m4a)
|
|
||||||
s3_object_name = f"{video_id}.{yt_result['ext']}"
|
s3_object_name = f"{video_id}.{yt_result['ext']}"
|
||||||
|
await bot.edit_message_text("☁️ Menyimpan ke Server...", chat_id, status_msg_id)
|
||||||
await bot.edit_message_text("☁️ Mengunggah ke MinIO Server...", chat_id, status_msg_id)
|
|
||||||
if local_file and os.path.exists(local_file):
|
if local_file and os.path.exists(local_file):
|
||||||
t_s3_up = time.time()
|
|
||||||
await upload_audio(local_file, s3_object_name)
|
await upload_audio(local_file, s3_object_name)
|
||||||
await db.save_cache(video_id, title, s3_object_name)
|
await db.save_cache(video_id, title, s3_object_name)
|
||||||
logger.info(f"⏱️ Upload ke MinIO memakan waktu: {time.time() - t_s3_up:.2f} detik")
|
|
||||||
|
|
||||||
# 3. KIRIM KE TELEGRAM
|
await bot.edit_message_text("📤 Mengirim ke Telegram...", chat_id, status_msg_id)
|
||||||
await bot.edit_message_text("📤 Mengirim file ke Telegram...", chat_id, status_msg_id)
|
|
||||||
if local_file and os.path.exists(local_file):
|
if local_file and os.path.exists(local_file):
|
||||||
t_tele_start = time.time()
|
|
||||||
audio = FSInputFile(local_file)
|
audio = FSInputFile(local_file)
|
||||||
await bot.send_audio(chat_id=chat_id, audio=audio, title=title, performer="Music Bot", request_timeout=300)
|
await bot.send_audio(chat_id=chat_id, audio=audio, title=title, performer="Music Bot", request_timeout=300)
|
||||||
logger.info(f"⏱️ Upload ke Telegram memakan waktu: {time.time() - t_tele_start:.2f} detik")
|
|
||||||
|
|
||||||
if status_msg_id: await bot.delete_message(chat_id, status_msg_id)
|
if status_msg_id: await bot.delete_message(chat_id, status_msg_id)
|
||||||
|
|
||||||
# 4. SUGGEST 5 LAGU (Judul lebih panjang)
|
# --- TAMPILKAN 30 REKOMENDASI DENGAN PAGING ---
|
||||||
t_recs = time.time()
|
|
||||||
recs = await get_recommendations(video_id)
|
recs = await get_recommendations(video_id)
|
||||||
logger.info(f"⏱️ Fetch Rekomendasi memakan waktu: {time.time() - t_recs:.2f} detik")
|
|
||||||
|
|
||||||
if recs:
|
if recs:
|
||||||
kb = []
|
user_searches[chat_id] = {
|
||||||
for rec in recs[:5]:
|
'header': f"💡 Rekomendasi lanjutan dari: **{title}**",
|
||||||
short_title = rec['title'][:60] + "..." if len(rec['title']) > 60 else rec['title']
|
'results': recs
|
||||||
kb.append([InlineKeyboardButton(text=f"▶️ {short_title}", callback_data=f"play_{rec['id']}")])
|
}
|
||||||
reply_markup = InlineKeyboardMarkup(inline_keyboard=kb)
|
kb = get_search_keyboard(chat_id, page=0)
|
||||||
await bot.send_message(chat_id, text=f"💡 **Rekomendasi dari:**\n_{title}_", reply_markup=reply_markup, parse_mode="Markdown")
|
if kb:
|
||||||
|
await bot.send_message(chat_id, text=f"{user_searches[chat_id]['header']}\n_Halaman 1_", reply_markup=kb, parse_mode="Markdown")
|
||||||
logger.info(f"✅ TOTAL WAKTU E2E: {time.time() - total_start_time:.2f} detik\n{'-'*40}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error: {e}")
|
logger.error(f"Error: {e}")
|
||||||
@@ -163,7 +266,7 @@ async def main():
|
|||||||
scheduler = AsyncIOScheduler()
|
scheduler = AsyncIOScheduler()
|
||||||
scheduler.add_job(cleanup_expired_cache, 'cron', hour=3, minute=0)
|
scheduler.add_job(cleanup_expired_cache, 'cron', hour=3, minute=0)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
logger.info("🚀 Bot Music Turbo Berjalan!")
|
logger.info("🚀 Bot Music V4 (Admin Approval & Super Paging) Berjalan!")
|
||||||
await bot.delete_webhook(drop_pending_updates=True)
|
await bot.delete_webhook(drop_pending_updates=True)
|
||||||
await dp.start_polling(bot)
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|||||||
10
cookies.txt
10
cookies.txt
@@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-1PAPISID jX9HIeAZEXX3bP9f/AopL252XpVmN6w-fU
|
.youtube.com TRUE / TRUE 2147483647 __Secure-1PAPISID jX9HIeAZEXX3bP9f/AopL252XpVmN6w-fU
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSID g.a0008AhqPQ4vlKVPuJB4BY8zzdimzFGpjCZI3PtorsbzCvPCb3-UNiEKAu2Nem3IRBTeL7M4jQACgYKAWsSARESFQHGX2MiBR4lu_TMGVkDg6nuR2v3KxoVAUF8yKpUhZ9s0gTmijqUQGCrrAbz0076
|
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSID g.a0008AhqPQ4vlKVPuJB4BY8zzdimzFGpjCZI3PtorsbzCvPCb3-UNiEKAu2Nem3IRBTeL7M4jQACgYKAWsSARESFQHGX2MiBR4lu_TMGVkDg6nuR2v3KxoVAUF8yKpUhZ9s0gTmijqUQGCrrAbz0076
|
||||||
.youtube.com TRUE / TRUE 1806024377 __Secure-1PSIDCC AKEyXzWzicfhafmNedOTnB5yBriXagyKfyIEQYIkhivz2G_stNbGK8uupR-Q9WaCEF6zOz1d
|
.youtube.com TRUE / TRUE 1806026697 __Secure-1PSIDCC AKEyXzVzj8BHVLDosFha11ymmFBOV4r1hRS1ta4nWZcHgZkBacg6rhh1hkprOJcA7YSDScfX
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDRTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDRTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-3PAPISID jX9HIeAZEXX3bP9f/AopL252XpVmN6w-fU
|
.youtube.com TRUE / TRUE 2147483647 __Secure-3PAPISID jX9HIeAZEXX3bP9f/AopL252XpVmN6w-fU
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSID g.a0008AhqPQ4vlKVPuJB4BY8zzdimzFGpjCZI3PtorsbzCvPCb3-UVw0LJPNy1xBeKd3GjL_nrgACgYKAd8SARESFQHGX2Mi4oiwDW0agUlDxti69hv6zBoVAUF8yKqTGo8oCprmktCz7NrkNVpx0076
|
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSID g.a0008AhqPQ4vlKVPuJB4BY8zzdimzFGpjCZI3PtorsbzCvPCb3-UVw0LJPNy1xBeKd3GjL_nrgACgYKAd8SARESFQHGX2Mi4oiwDW0agUlDxti69hv6zBoVAUF8yKqTGo8oCprmktCz7NrkNVpx0076
|
||||||
.youtube.com TRUE / TRUE 1806024377 __Secure-3PSIDCC AKEyXzXoNsYyfFthEbcFq88fQFsmt-Qraq1XS_2EA6ZkjjP3cNn0AEt1tF2sVtRrGU0CjWzwvg
|
.youtube.com TRUE / TRUE 1806026697 __Secure-3PSIDCC AKEyXzURj6FH3Hh60cp98zUCM49GiTZJ9mcBkypQpiCq5neND5iTw7dWvfmk3CCUrnyqDksmyw
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDRTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDRTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDTS sidts-CjYBWhotCR9GvMAvgfIj2VO7w2GS8jUm45hvgjK75mkpM7qlEO-z2RIGvuP3FX0ApZ3UEnRHAaIQAA
|
||||||
.youtube.com TRUE / TRUE 2147483647 __Secure-ROLLOUT_TOKEN CMbM-7XNqZzV5QEQmYbS_rO8kwMY5PKr_7O8kwM%3D
|
.youtube.com TRUE / TRUE 2147483647 __Secure-ROLLOUT_TOKEN CMbM-7XNqZzV5QEQmYbS_rO8kwMY5PKr_7O8kwM%3D
|
||||||
@@ -18,6 +18,6 @@
|
|||||||
.youtube.com TRUE / TRUE 2147483647 LOGIN_INFO AFmmF2swRAIgDHRqApvISbyAZq7Jw7ACZDaYENyiLfRJhkSxgsaSzHUCIDBqEp7Lla6YL_9yHKzX5OuwRv6eSuDP7wN4eu2h8boE:QUQ3MjNmenlDUldaUlpCVmlMc2ZFdWxtWVlzX2lVRTRic2o4djgzSlJiaHhHYnN4dmh0YTU2NFdpS1lRSkZIUlN3RVBRWmZMTFdTU1pjQ3gtU09LTmRUX3l6WEpCWGdpeFpiVkptS1BHRlBlQXU1R0RXZjR3cnR4LW94YlREbzFITzdOaWctTEJLcjN5UWpCM3VXVTQzUWZiMl9RLXhNRERR
|
.youtube.com TRUE / TRUE 2147483647 LOGIN_INFO AFmmF2swRAIgDHRqApvISbyAZq7Jw7ACZDaYENyiLfRJhkSxgsaSzHUCIDBqEp7Lla6YL_9yHKzX5OuwRv6eSuDP7wN4eu2h8boE:QUQ3MjNmenlDUldaUlpCVmlMc2ZFdWxtWVlzX2lVRTRic2o4djgzSlJiaHhHYnN4dmh0YTU2NFdpS1lRSkZIUlN3RVBRWmZMTFdTU1pjQ3gtU09LTmRUX3l6WEpCWGdpeFpiVkptS1BHRlBlQXU1R0RXZjR3cnR4LW94YlREbzFITzdOaWctTEJLcjN5UWpCM3VXVTQzUWZiMl9RLXhNRERR
|
||||||
.youtube.com TRUE / TRUE 2147483647 NID 530=J1je9vBs7k1AZSF4I6E-32SX_vniEVSvWt6vRJC19rznxuHBniKAUk91MSxJ9KY6sPEIGqw29RYQvsglruzNcyGUpG4RIb4Fk50UUrFHbXG28v34nj9rkikmhXi7N84D_-1l8im9-Qm9zkPfxKaL8V8xBaBjK6a1p2et2q3_n5wM0hOI7R24Gw4hQbX9Rcj-oUTuBbvIxb_VevmQ
|
.youtube.com TRUE / TRUE 2147483647 NID 530=J1je9vBs7k1AZSF4I6E-32SX_vniEVSvWt6vRJC19rznxuHBniKAUk91MSxJ9KY6sPEIGqw29RYQvsglruzNcyGUpG4RIb4Fk50UUrFHbXG28v34nj9rkikmhXi7N84D_-1l8im9-Qm9zkPfxKaL8V8xBaBjK6a1p2et2q3_n5wM0hOI7R24Gw4hQbX9Rcj-oUTuBbvIxb_VevmQ
|
||||||
.youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC
|
.youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC
|
||||||
.youtube.com TRUE / TRUE 0 YSC YNBoD1tlo5I
|
.youtube.com TRUE / TRUE 0 YSC 6K5oY8QB4bs
|
||||||
.youtube.com TRUE / TRUE 1790040377 VISITOR_INFO1_LIVE H2nqd5gOIN0
|
.youtube.com TRUE / TRUE 1790042697 VISITOR_INFO1_LIVE JbfxjpcD228
|
||||||
.youtube.com TRUE / TRUE 1790040377 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgYQ%3D%3D
|
.youtube.com TRUE / TRUE 1790042697 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgXw%3D%3D
|
||||||
|
|||||||
42
yt_engine.py
42
yt_engine.py
@@ -25,7 +25,7 @@ def generate_netscape_cookies():
|
|||||||
print(f"Error parsing cookies: {e}")
|
print(f"Error parsing cookies: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def fetch_recommendations_sync(video_id: str, limit: int = 5):
|
def fetch_recommendations_sync(video_id: str, limit: int = 30):
|
||||||
ydl_opts_recs = {
|
ydl_opts_recs = {
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
'extract_flat': True,
|
'extract_flat': True,
|
||||||
@@ -93,3 +93,43 @@ def search_and_download(query_or_url: str):
|
|||||||
|
|
||||||
async def process_youtube_request(query: str):
|
async def process_youtube_request(query: str):
|
||||||
return await asyncio.to_thread(search_and_download, query)
|
return await asyncio.to_thread(search_and_download, query)
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# FITUR TAMBAHAN: PENCARIAN LIST LAGU
|
||||||
|
# ==========================================
|
||||||
|
def fetch_search_results_sync(query: str, max_results: int = 30):
|
||||||
|
"""Mengambil daftar hasil pencarian tanpa mendownload audionya"""
|
||||||
|
ydl_opts_search = {
|
||||||
|
'quiet': True,
|
||||||
|
'extract_flat': True, # Mengambil metadata saja biar super cepat
|
||||||
|
# KITA HAPUS extractor_args (player_client) di sini karena bikin search error
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.path.exists("cookies.txt"):
|
||||||
|
ydl_opts_search['cookiefile'] = 'cookies.txt'
|
||||||
|
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(ydl_opts_search) as ydl:
|
||||||
|
# Memaksa yt-dlp menggunakan mode pencarian (ytsearch) secara eksplisit
|
||||||
|
search_query = f"ytsearch{max_results}:{query}"
|
||||||
|
info = ydl.extract_info(search_query, download=False)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
if 'entries' in info:
|
||||||
|
for entry in info['entries']:
|
||||||
|
if entry and entry.get('id') and entry.get('title'):
|
||||||
|
# Terkadang di hasil pencarian nama key-nya 'channel', bukan 'uploader'
|
||||||
|
uploader_name = entry.get('uploader') or entry.get('channel') or 'Unknown'
|
||||||
|
results.append({
|
||||||
|
'id': entry['id'],
|
||||||
|
'title': entry['title'],
|
||||||
|
'uploader': uploader_name
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Search error: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def search_youtube_list(query: str, max_results: int = 30):
|
||||||
|
return await asyncio.to_thread(fetch_search_results_sync, query, max_results)
|
||||||
|
|||||||
Reference in New Issue
Block a user