Compare commits

..

7 Commits

Author SHA1 Message Date
YOLANDO
277ef128ac update cookies 2026-03-27 09:48:13 +07:00
YOLANDO
61d721b514 approver 2026-03-26 09:05:08 +07:00
YOLANDO
2e2b10890c init awal 30 lagu 2026-03-26 08:58:08 +07:00
YOLANDO
4051b0f054 update cookies 2026-03-26 08:30:41 +07:00
YOLANDO
c710a9bdd5 fix final 2026-03-25 16:05:26 +07:00
YOLANDO
b5b6135cc3 final 1 2026-03-25 12:37:26 +07:00
YOLANDO
f962f16308 v2 2026-03-25 11:44:55 +07:00
11 changed files with 346 additions and 208 deletions

5
.env
View File

@@ -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=1121430909
# --- POSTGRESQL --- # --- POSTGRESQL ---
# Gunakan 'localhost' jika aplikasi jalan langsung di VPS (tanpa Docker), # Gunakan 'localhost' jika aplikasi jalan langsung di VPS (tanpa Docker),
@@ -15,7 +16,7 @@ DB_NAME=default_db
# --- MINIO S3 --- # --- MINIO S3 ---
# Catatan: Pastikan port 9000 (API MinIO) bisa diakses, bukan cuma port 9001 (Console) # Catatan: Pastikan port 9000 (API MinIO) bisa diakses, bukan cuma port 9001 (Console)
# Endpoint tidak perlu memakai awalan https:// atau http:// # Endpoint tidak perlu memakai awalan https:// atau http://
MINIO_ENDPOINT=localhost:9000 MINIO_ENDPOINT=127.0.0.1:9000
MINIO_ACCESS_KEY=admin_ando MINIO_ACCESS_KEY=admin_ando
MINIO_SECRET_KEY=PasswordSuperKuat123! MINIO_SECRET_KEY=PasswordSuperKuat123!
# SECURE diset ke True karena lalu lintasnya dilewatkan melalui NPM yang menggunakan HTTPS (Let's Encrypt). # SECURE diset ke True karena lalu lintasnya dilewatkan melalui NPM yang menggunakan HTTPS (Let's Encrypt).

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
downloads

304
bot.py
View File

@@ -1,164 +1,272 @@
import asyncio import asyncio
import os import os
import time
import logging import logging
from aiogram import Bot, Dispatcher, types, BaseMiddleware import re
from aiogram import Bot, Dispatcher, types, BaseMiddleware, F
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.types import FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.client.session.aiohttp import AiohttpSession # <-- TAMBAH INI 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
# Import modul lokal yang sudah kita buat from yt_engine import process_youtube_request, get_recommendations, search_youtube_list
from yt_engine import process_youtube_request
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
# Setup Logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
load_dotenv() load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN") BOT_TOKEN = os.getenv("BOT_TOKEN")
# Pastikan ini mengambil angka ID Telegram kamu
AUTHORIZED_USER_ID = int(os.getenv("AUTHORIZED_USER_ID", 0))
# --- INISIALISASI BOT --- # --- KONFIGURASI ADMIN & USERS ---
# Timeout diatur 300 detik (5 menit) agar Telegram tidak memutus koneksi saat mengirim file MP3 yang besar 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()
# --- MIDDLEWARE / SECURITY CHECK --- music_queue = asyncio.Queue()
user_searches = {}
async def queue_worker():
while True:
query, chat_id, msg_id = await music_queue.get()
try:
await process_music_request(query, chat_id, msg_id)
except Exception as e:
logger.error(f"Worker Error: {e}")
finally:
music_queue.task_done()
# --- SATPAM BOT DENGAN FITUR NOTIFIKASI ADMIN ---
class SecurityMiddleware(BaseMiddleware): class SecurityMiddleware(BaseMiddleware):
async def __call__(self, handler, event: types.Message, data: dict): async def __call__(self, handler, event: types.Message | CallbackQuery, data: dict):
# Memblokir semua orang kecuali Kamu (Single-User Mode) user_id = event.from_user.id
if event.from_user.id != AUTHORIZED_USER_ID:
logger.warning(f"Akses ditolak untuk User ID: {event.from_user.id}") if user_id not in AUTHORIZED_USER_IDS:
return # Abaikan pesan # 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)
# Daftarkan Satpam ke sistem
dp.message.middleware(SecurityMiddleware()) dp.message.middleware(SecurityMiddleware())
dp.callback_query.middleware(SecurityMiddleware())
# --- 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):
match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", query)
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)
# --- COMMAND HANDLERS ---
@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( await message.answer("🎧 **Music Bot VIP Ready!**\nKirim format: `/play <judul lagu>`", parse_mode="Markdown")
"🎧 **Kantor-Bypass Music Bot Ready!**\n\n"
"Kirim judul lagu atau link YouTube dengan format:\n"
"`/play <judul/link>`\n\n"
"Contoh: `/play Dewa 19 Kangen`"
)
@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: if not query: return await message.answer("⚠️ Masukkan judul lagu.")
return await message.answer("⚠️ Masukkan judul lagu. Contoh: `/play Nadin Amizah`")
# Kirim status loading status_msg = await message.answer("🔍 *Mencari daftar lagu...*", parse_mode="Markdown")
status_msg = await message.answer("🔍 *Mencari dan memproses...*") results = await search_youtube_list(query, max_results=30)
# Gunakan asyncio.create_task agar tidak nge-block queue bot (bisa antre banyak lagu) if not results: return await status_msg.edit_text("❌ Lagu tidak ditemukan.")
asyncio.create_task(process_music_request(query, message.chat.id, status_msg.message_id))
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_"))
async def handle_suggestion_click(callback: CallbackQuery):
video_id = callback.data.split("_", 1)[1]
await callback.message.edit_reply_markup(reply_markup=None)
status_msg = await bot.send_message(callback.message.chat.id, "⏳ Mengunduh lagu pilihanmu...")
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 callback.answer("Siapp! Diproses...")
# --- CORE LOGIC: PROCESS MUSIC ---
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 # Mencegah error 'UnboundLocalError' jika gagal di awal local_file = None
try: try:
# 1. Download dari YouTube video_id = extract_video_id(query)
yt_result = await process_youtube_request(query) cached_data = None
if video_id:
cached_data = await db.get_cache(video_id)
if cached_data:
title = cached_data.get('title', 'Lagu')
s3_object_name = cached_data.get('s3_object_key', f"{video_id}.m4a")
ext = s3_object_name.split('.')[-1]
local_file = f"downloads/cache_{video_id}.{ext}"
await bot.edit_message_text("⚡ Mengambil dari Cache...", chat_id, status_msg_id)
await download_audio(s3_object_name, local_file)
if not cached_data:
await bot.edit_message_text("🔍 Mengunduh dari YouTube...", chat_id, status_msg_id)
yt_result = await process_youtube_request(query)
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"]
s3_object_name = f"{video_id}.mp3" s3_object_name = f"{video_id}.{yt_result['ext']}"
await bot.edit_message_text("☁️ Menyimpan ke Server...", chat_id, status_msg_id)
# 2. Cek Cache di Database
cached_data = await db.get_cache(video_id)
if cached_data:
await bot.edit_message_text("⚡ *Mengambil dari Cache S3...*", chat_id, status_msg_id)
# Download dari S3 ke local temporary
local_file = f"downloads/cache_{video_id}.mp3"
await download_audio(s3_object_name, local_file)
else:
await bot.edit_message_text("☁️ *Mengunggah ke S3 Storage...*", chat_id, status_msg_id)
# Upload file baru ke S3
if local_file and os.path.exists(local_file): if local_file and os.path.exists(local_file):
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)
# 3. Kirim Audio ke Telegram await bot.edit_message_text("📤 Mengirim ke Telegram...", chat_id, status_msg_id)
await bot.edit_message_text("📤 *Mengirim audio ke Telegram... (Mungkin butuh waktu)*", chat_id, status_msg_id)
if local_file and os.path.exists(local_file): if local_file and os.path.exists(local_file):
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)
if status_msg_id: await bot.delete_message(chat_id, status_msg_id)
# Opsi: Tombol pencarian selanjutnya # --- TAMPILKAN 30 REKOMENDASI DENGAN PAGING ---
kb = [[InlineKeyboardButton(text="🎵 Putar Lagu Acak Lainnya", switch_inline_query_current_chat="")]] recs = await get_recommendations(video_id)
reply_markup = InlineKeyboardMarkup(inline_keyboard=kb) if recs:
user_searches[chat_id] = {
# Ekstra Pengaman Timeout di Request Level 'header': f"💡 Rekomendasi lanjutan dari: **{title}**",
await bot.send_audio( 'results': recs
chat_id=chat_id, }
audio=audio, kb = get_search_keyboard(chat_id, page=0)
title=title, if kb:
performer="Music Bot", await bot.send_message(chat_id, text=f"{user_searches[chat_id]['header']}\n_Halaman 1_", reply_markup=kb, parse_mode="Markdown")
reply_markup=reply_markup,
request_timeout=300
)
# Hapus pesan status yang muter-muter
await bot.delete_message(chat_id, status_msg_id)
else:
await bot.edit_message_text("❌ File audio tidak ditemukan di server lokal.", chat_id, status_msg_id)
except Exception as e: except Exception as e:
logger.error(f"Error processing music: {e}") logger.error(f"Error: {e}")
try:
await bot.edit_message_text(f"❌ Terjadi kesalahan internal: {e}", chat_id, status_msg_id)
except:
pass # Abaikan jika pesan sudah terhapus
finally: finally:
# 4. Hapus file temporary di VPS (Hemat Disk!) if local_file and os.path.exists(local_file): os.remove(local_file)
if local_file and os.path.exists(local_file):
os.remove(local_file)
# --- BACKGROUND JOB: AUTO CLEANUP (7 HARI) ---
async def cleanup_expired_cache(): async def cleanup_expired_cache():
logger.info("🧹 Menjalankan tugas pembersihan cache otomatis...")
expired_items = await db.get_expired_cache(days=7) expired_items = await db.get_expired_cache(days=7)
for item in expired_items: for item in expired_items:
# Hapus dari S3
await delete_audio(item['s3_object_key']) await delete_audio(item['s3_object_key'])
# Hapus dari Database
await db.delete_cache(item['youtube_id']) await db.delete_cache(item['youtube_id'])
logger.info(f"🗑️ Dihapus: {item['title']} (Usia > 7 Hari)")
# --- MAIN LOOP ---
async def main(): async def main():
# Konek ke database
await db.connect() await db.connect()
asyncio.create_task(queue_worker())
# Jalankan Scheduler untuk bersih-bersih tiap jam 3 pagi
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 V4 (Admin Approval & Super Paging) Berjalan!")
logger.info("🚀 Bot Music Started!")
# Hapus webhook lama (jika ada) dan mulai polling
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)

View File

@@ -1,25 +1,26 @@
# Netscape HTTP Cookie File # Netscape HTTP Cookie File
# This file is generated by yt-dlp. Do not edit. # This file is generated by yt-dlp. Do not edit.
.youtube.com TRUE / TRUE 2147483647 __Secure-1PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .youtube.com TRUE / TRUE 2147483647 __Secure-1PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSID g.a0008AhqPWcg3-Syf1o_JEl3LCHzWFKn_n3Y8jgh3RnwDW83PtpvDZ6ZjTqI52V0CZWm40gj1wACgYKAYYSARESFQHGX2MigEiR2_gNFFV-QFRM2YthnxoVAUF8yKp0aiEfXAKcPOTZdsLVsdG00076 .youtube.com TRUE / TRUE 2147483647 __Secure-1PSID g.a0008QhqPaEYewnBjrrVkAfyYUX4qZBvGlP0AAKy4GHNUANHIhILSu3fanNWkdZCLU-xY2lVkgACgYKAZkSARESFQHGX2Miw30Iyoiest2hLNRbwo7xrBoVAUF8yKoPd2FfK6VDhHMeP0AeTiHq0076
.youtube.com TRUE / TRUE 1805947740 __Secure-1PSIDCC AKEyXzUORFVg_0ZBDrcdDFVznCjghYk0kFgXPgu6_nGbfqQb8JQApHGnBqt7NkD7fgCea-TTmg .youtube.com TRUE / TRUE 1806115265 __Secure-1PSIDCC AKEyXzWNkpsXnpDnuLA3W_IHPrWB2-z9uWItNPEWogkNMH6RR3EmgVtXL8guFpgBVBPTATfXbg
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDRTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDRTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA
.youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDTS sidts-CjUBWhotCXH5m0kFaAC1lhqf8kaotcZDxFxOty6XBxNsYxySai6J15Fi8BHklGtKOGZKSNYrLxAA .youtube.com TRUE / TRUE 2147483647 __Secure-1PSIDTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA
.youtube.com TRUE / TRUE 1837483738 __Secure-3PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .youtube.com TRUE / TRUE 2147483647 __Secure-3PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w
.youtube.com TRUE / TRUE 1837483738 __Secure-3PSID g.a0008AhqPWcg3-Syf1o_JEl3LCHzWFKn_n3Y8jgh3RnwDW83PtpvC14W2htdab0jQBsnf1YkqgACgYKAdcSARESFQHGX2MiTK5ZxIBltpFTHCsEt_xiuxoVAUF8yKrG5FgxNyKwsIayRODxp8Fo0076 .youtube.com TRUE / TRUE 2147483647 __Secure-3PSID g.a0008QhqPaEYewnBjrrVkAfyYUX4qZBvGlP0AAKy4GHNUANHIhILfnT7eNSgF_8dabeD7hB4KQACgYKARISARESFQHGX2MiQZ9X8qjH9mx4cMVrf6sfSRoVAUF8yKqWhqmWybq0wXdDiyiB4j2A0076
.youtube.com TRUE / TRUE 1805947740 __Secure-3PSIDCC AKEyXzVld0GspLJe8l6JS4cfa4h4ElnWgBoAB5kiHdSs0gCerB-quDuuc4JEn63Voja8hi9ZSQ .youtube.com TRUE / TRUE 1806115265 __Secure-3PSIDCC AKEyXzUcbRIRHbwhE3LWL4wDgXuVGsXcWgERJXuPBpaDFfSANgOgmmYKjsM9laeZYKVJojrQUyc
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDRTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDRTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA
.youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDTS sidts-CjUBWhotCXH5m0kFaAC1lhqf8kaotcZDxFxOty6XBxNsYxySai6J15Fi8BHklGtKOGZKSNYrLxAA .youtube.com TRUE / TRUE 2147483647 __Secure-3PSIDTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA
.youtube.com TRUE / TRUE 2147483647 __Secure-BUCKET CJsE .youtube.com TRUE / TRUE 2147483647 __Secure-BUCKET CPYF
.youtube.com TRUE / TRUE 2147483647 __Secure-ROLLOUT_TOKEN CPuykLucktiDWBCKzZ6okLmLAximj_KpgbqTAw%3D%3D .youtube.com TRUE / TRUE 2147483647 __Secure-ROLLOUT_TOKEN CMbM-7XNqZzV5QEQmYbS_rO8kwMY_rSr_fW-kwM%3D
.youtube.com TRUE / TRUE 2147483647 _gcl_au 1.1.804233229.1768921729 .youtube.com TRUE / TRUE 2147483647 AEC AaJma5uOowvEPsKVftwErbdidI19zMVl5C9-xa4RyR78tyxULUO9ytInv40
.youtube.com TRUE / TRUE 2147483647 AEC AaJma5u8FgHY34zVcINRn3aV7kclTK8WLQNF5t6Vx4CV-lCCTfNWLQLU_A .youtube.com TRUE / TRUE 2147483647 APC AfxxVi5bzd9v-P9K_jo26VgiosIxzE813GYPLYyC5gmUMYjiaXg36w
.youtube.com TRUE / TRUE 2147483647 APC AfxxVi7ddIpw9dyE7CafpiBRx2fLbINQXwCWfbOjJujb7m4E7VjYkg .youtube.com TRUE / TRUE 2147483647 APISID OnSMY049lYtHikQy/A5cj2ru6POklKZoFC
.youtube.com TRUE / TRUE 2147483647 APISID PbWG-XrqTeJJFiPn/ACQ0hTzgL5lCMuKP5 .youtube.com TRUE / TRUE 2147483647 GPS 1
.youtube.com TRUE / TRUE 2147483647 HSID AvGoSoPSULrXuoRT9
.youtube.com TRUE / TRUE 2147483647 IDE AHWqTUmyGzw548Vz7XpBhz59fxCsE3RWJDZPUh3zrwecOjSQ1O8bujmlLDEeiEqCdE8
.youtube.com TRUE / TRUE 2147483647 LOGIN_INFO AFmmF2swQwIfXTYVZVbXCVUR4NikS0IeA8v1bJ89WfHBNfr5ipEBGQIgK6RN_6Ubyj7GXQEy9AxZ9tofqAHiELNQ9NP0xRUrZL0:QUQ3MjNmejU3NEZORFNHTk5MM1BBam9mTm9sTGlLVXRSVk1qa3ExU0dkRlFHMjZMSUNwZlZrRFRmcUIxNHZ4S1p5WG5FbkEzSnNhQ3NITVhxSW5NVVBoc3BaV1N1OTFBYXd4cFdMZ2NNcXEwMFY4UEZsVmZLckNWMGtOVUFyRW5DUEdtLU9KX3pyMGZDWUdZdWhzejlIUWwtOUlyOEdNUnN3
.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 SOCS CAI .youtube.com TRUE / TRUE 0 YSC YlhQVvcChl0
.youtube.com TRUE / TRUE 0 YSC a2WtW-hP4Ck .youtube.com TRUE / TRUE 1790131265 VISITOR_INFO1_LIVE yYevOjATcc8
.youtube.com TRUE / TRUE 1789963740 VISITOR_INFO1_LIVE BIF7sNwBpLI .youtube.com TRUE / TRUE 1790131265 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgTw%3D%3D
.youtube.com TRUE / TRUE 1789963740 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgFA%3D%3D
.youtube.com TRUE / TRUE 1837483740 LOGIN_INFO AFmmF2swRgIhAMFCn3UGB5h-2Wf8Rnv4LIWp0LiVwf6ibiPVQhmFufMQAiEA-q7C22E801epMI4Lk54Xb9uNUHWVKkjNLHJ-auj0p7c:QUQ3MjNmelp0RkxSamFrTTZnOVBLelN1ZjVpeVRBWC1TTUc1d0NPUm9PXzNPQmx0dUNEdjBDOEF0cGsxc3p1SWhwMWtIZDdyMjRxYVFKZWRIWnkwMkxzLWVnakVCdFlxcVpFclc3N3VfNmpIWWh2aUd2eUxydzJMekZ6eFo4MzZUMDJROVRtTHZXdjFyZGlqV3dWVFZnUTlTWWJvbTZHRHRR

BIN
downloads/7SqNVv98e8Q.mp3 Normal file

Binary file not shown.

BIN
downloads/grp6FCnioMM.mp3 Normal file

Binary file not shown.

BIN
downloads/gvunApwKIiY.mp3 Normal file

Binary file not shown.

BIN
downloads/gvunApwKIiY.webm Normal file

Binary file not shown.

View File

@@ -1,30 +1,30 @@
__Secure-1PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .google.com / 2027-04-29T01:16:45.016Z 51 ✓ High __Secure-1PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w .google.co.id / 2027-05-01T01:26:56.717Z 51 ✓ High
__Secure-1PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .google.co.id / 2027-04-29T01:16:47.771Z 51 ✓ High __Secure-1PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w .google.com / 2027-05-01T01:26:55.647Z 51 ✓ High
__Secure-1PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .youtube.com / 2027-04-29T01:16:46.387Z 51 ✓ High __Secure-1PSID g.a0008QhqPejw13d8Bas4NdqKxoWCDD00TsMPF7Knc2ottj33ssYPm7cjYs1E8yUJXQ9IZsAengACgYKATgSARESFQHGX2MijdbKOp-mlWEL91PAMktjThoVAUF8yKpXrlV2MNU0xOiEf0WSNWup0076 .google.co.id / 2027-05-01T01:26:56.717Z 167 ✓ ✓ High
__Secure-1PSID g.a0008AhqPRgOQo5F8wOanRJB1db-thi1yhD1Xwlfzu9ppmi4kdMvzqGjBiiYDCNImYvcK_KXVgACgYKAXwSARESFQHGX2Mir1uyxrt2SQU3jrmD0yIthBoVAUF8yKq28ua4eVfoLokWn8CKTPoa0076 .youtube.com / 2027-04-29T01:16:46.387Z 167 ✓ ✓ High __Secure-1PSID g.a0008QhqPaEYewnBjrrVkAfyYUX4qZBvGlP0AAKy4GHNUANHIhILSu3fanNWkdZCLU-xY2lVkgACgYKAZkSARESFQHGX2Miw30Iyoiest2hLNRbwo7xrBoVAUF8yKoPd2FfK6VDhHMeP0AeTiHq0076 .google.com / 2027-05-01T01:26:56.205Z 167 ✓ ✓ High
__Secure-1PSID g.a0008AhqPRgOQo5F8wOanRJB1db-thi1yhD1Xwlfzu9ppmi4kdMvzqGjBiiYDCNImYvcK_KXVgACgYKAXwSARESFQHGX2Mir1uyxrt2SQU3jrmD0yIthBoVAUF8yKq28ua4eVfoLokWn8CKTPoa0076 .google.co.id / 2027-04-29T01:16:47.771Z 167 ✓ ✓ High __Secure-1PSIDCC AKEyXzUAwHyvaa8__7v4HjDYX86BY3ua4rZyVIzG0ja6gavUQP4WL3HLSUvuIcsltx_jqSk9xA .google.com / 2027-03-27T02:39:37.490Z 90 ✓ ✓ High
__Secure-1PSID g.a0008AhqPWcg3-Syf1o_JEl3LCHzWFKn_n3Y8jgh3RnwDW83PtpvDZ6ZjTqI52V0CZWm40gj1wACgYKAYYSARESFQHGX2MigEiR2_gNFFV-QFRM2YthnxoVAUF8yKp0aiEfXAKcPOTZdsLVsdG00076 .google.com / 2027-04-29T01:16:45.579Z 167 ✓ ✓ High __Secure-1PSIDRTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA .google.com / 2026-03-27T02:46:46.685Z 101 ✓ ✓ High
__Secure-1PSIDCC AKEyXzXv0PIbX1eVJDr7Vttvo3tExsBC8rMs2vpVgrxNe00DfRJOHTiRJlKZ3pEvy00-Eayfsg .youtube.com / 2027-03-25T02:41:49.761Z 90 ✓ ✓ High __Secure-1PSIDTS sidts-CjQBWhotCR30ArqZTdTYPA9IaKLjiqxyQo3pFqyW0ahH99kAISY8Xx7eYW3SKziORlnuTslIEAA .youtube.com / 2027-03-27T02:39:38.397Z 97 ✓ ✓ High
__Secure-1PSIDCC AKEyXzXvPBT_qPOKMMyJTGDcKgJyzeumUh2o_XM_-pzE2LOejAK6BTqjxXwrYtvVmBkzOcnh1Q .google.com / 2027-03-25T02:42:51.868Z 90 ✓ ✓ High __Secure-1PSIDTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA .google.com / 2027-03-27T02:36:46.684Z 100 ✓ ✓ High
__Secure-1PSIDRTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .google.com / 2026-03-25T02:52:18.056Z 102 ✓ ✓ High __Secure-3PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w .youtube.com / 2027-05-01T02:39:38.398Z 51 ✓ None High
__Secure-1PSIDTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .google.com / 2027-03-25T02:42:18.056Z 101 ✓ High __Secure-3PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w .google.com / 2027-05-01T01:26:55.647Z 51 ✓ None High
__Secure-1PSIDTS sidts-CjUBWhotCXH5m0kFaAC1lhqf8kaotcZDxFxOty6XBxNsYxySai6J15Fi8BHklGtKOGZKSNYrLxAA .youtube.com / 2027-03-25T01:16:46.387Z 98 ✓ ✓ High __Secure-3PAPISID MfDYI460uMwbNWQX/AyiVY9j7HpzgoY-0w .google.co.id / 2027-05-01T01:26:56.717Z 51 ✓ None High
__Secure-3PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .google.co.id / 2027-04-29T01:16:47.771Z 51 ✓ None High __Secure-3PSID g.a0008QhqPejw13d8Bas4NdqKxoWCDD00TsMPF7Knc2ottj33ssYPtyc-n5f0oEWAodP032RkiQACgYKAVQSARESFQHGX2Miyfngp-pxu1zO6LsyiRhYLxoVAUF8yKqQ_Mt_VPGw8gmF1lmeF0Mz0076 .google.co.id / 2027-05-01T01:26:56.717Z 167 ✓ ✓ None High
__Secure-3PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .google.com / 2027-04-29T01:16:45.016Z 51 ✓ None High __Secure-3PSID g.a0008QhqPaEYewnBjrrVkAfyYUX4qZBvGlP0AAKy4GHNUANHIhILfnT7eNSgF_8dabeD7hB4KQACgYKARISARESFQHGX2MiQZ9X8qjH9mx4cMVrf6sfSRoVAUF8yKqWhqmWybq0wXdDiyiB4j2A0076 .google.com / 2027-05-01T01:26:56.205Z 167 ✓ ✓ None High
__Secure-3PAPISID CABFFJNnCe9Ocqz0/ANX4rXmqpbZzZy8y1 .youtube.com / 2027-04-29T01:16:46.387Z 51 ✓ None High __Secure-3PSID g.a0008QhqPaEYewnBjrrVkAfyYUX4qZBvGlP0AAKy4GHNUANHIhILfnT7eNSgF_8dabeD7hB4KQACgYKARISARESFQHGX2MiQZ9X8qjH9mx4cMVrf6sfSRoVAUF8yKqWhqmWybq0wXdDiyiB4j2A0076 .youtube.com / 2027-05-01T02:39:38.398Z 167 ✓ ✓ None High
__Secure-3PSID g.a0008AhqPRgOQo5F8wOanRJB1db-thi1yhD1Xwlfzu9ppmi4kdMvHGjo5dodXzHfz189Lq0cJAACgYKAYASARESFQHGX2MiLX3h-qgYtLWxidyIqDdTXxoVAUF8yKogpTMEeE4KkrIFdEdSM-w20076 .youtube.com / 2027-04-29T01:16:46.387Z 167 ✓ ✓ None High __Secure-3PSIDCC AKEyXzVeeDHS1lWGDCFuEFfj0Is6t0rrf7sdFyFo1MtTGdCCU8zHIEdbgvsv1lHyu7SApJif0AA .youtube.com / 2027-03-27T02:39:45.367Z 91 ✓ ✓ None High
__Secure-3PSID g.a0008AhqPWcg3-Syf1o_JEl3LCHzWFKn_n3Y8jgh3RnwDW83PtpvC14W2htdab0jQBsnf1YkqgACgYKAdcSARESFQHGX2MiTK5ZxIBltpFTHCsEt_xiuxoVAUF8yKrG5FgxNyKwsIayRODxp8Fo0076 .google.com / 2027-04-29T01:16:45.579Z 167 ✓ ✓ None High __Secure-3PSIDCC AKEyXzUChCCGljD9bILJOGLEfDcrdJaAiA7APxnV2QrTAW9glKnDQ0nbEYkOf-jR_dCUXkO7QKM .google.com / 2027-03-27T02:39:38.133Z 91 ✓ ✓ None High
__Secure-3PSID g.a0008AhqPRgOQo5F8wOanRJB1db-thi1yhD1Xwlfzu9ppmi4kdMvHGjo5dodXzHfz189Lq0cJAACgYKAYASARESFQHGX2MiLX3h-qgYtLWxidyIqDdTXxoVAUF8yKogpTMEeE4KkrIFdEdSM-w20076 .google.co.id / 2027-04-29T01:16:47.771Z 167 ✓ ✓ None High __Secure-3PSIDRTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA .google.com / 2026-03-27T02:46:46.685Z 101 ✓ ✓ None High
__Secure-3PSIDCC AKEyXzVz48drvwGgN1b2XAKs81pl9H2mM7nJHYNIIEbJC9fqGsjq6GA_F_67AqpwXAWM8EzMxg .youtube.com / 2027-03-25T02:41:49.761Z 90 ✓ ✓ None High __Secure-3PSIDTS sidts-CjQBWhotCR30ArqZTdTYPA9IaKLjiqxyQo3pFqyW0ahH99kAISY8Xx7eYW3SKziORlnuTslIEAA .youtube.com / 2027-03-27T02:39:38.398Z 97 ✓ ✓ None High
__Secure-3PSIDCC AKEyXzXWcQoWLgU8-WQAC97HQEgo_Xv1BD5ov5jpXypXRQBggzdmNsu9hamArLQJDmo-Kvyy0A .google.com / 2027-03-25T02:42:51.868Z 90 ✓ ✓ None High __Secure-3PSIDTS sidts-CjYBWhotCVbg4zL386MEd1xPDnIrXetv9dZJvT7F0t8IruwIx6ZlfvbmmOFnYYLIOBUQlwwdCggQAA .google.com / 2027-03-27T02:36:46.685Z 100 ✓ ✓ None High
__Secure-3PSIDRTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .google.com / 2026-03-25T02:52:18.057Z 102 ✓ ✓ None High __Secure-BUCKET CPYF .google.com / 2026-09-22T01:33:55.289Z 19 ✓ ✓ Medium
__Secure-3PSIDTS sidts-CjcBWhotCb-4c6IuQXRvmRVYyon3cb4LfaOkQmhdlWz2FTjey8tLFTyGymAQr1fbHPACgLGOXKsXEAA .google.com / 2027-03-25T02:42:18.056Z 101 ✓ ✓ None High __Secure-ROLLOUT_TOKEN CMbM-7XNqZzV5QEQmYbS_rO8kwMY_rSr_fW-kwM%3D .youtube.com / 2026-09-23T01:25:16.915Z 64 ✓ ✓ None https://youtube.com Medium
__Secure-3PSIDTS sidts-CjUBWhotCXH5m0kFaAC1lhqf8kaotcZDxFxOty6XBxNsYxySai6J15Fi8BHklGtKOGZKSNYrLxAA .youtube.com / 2027-03-25T01:16:46.387Z 98 ✓ ✓ None High AEC AaJma5uOowvEPsKVftwErbdidI19zMVl5C9-xa4RyR78tyxULUO9ytInv40 .google.com / 2026-09-22T01:50:18.899Z 62 ✓ ✓ Lax Medium
__Secure-BUCKET CJsE .google.com / 2026-06-09T10:41:45.999Z 19 ✓ ✓ Medium APC AfxxVi5bzd9v-P9K_jo26VgiosIxzE813GYPLYyC5gmUMYjiaXg36w .doubleclick.net / 2026-09-22T01:24:54.796Z 57 ✓ None https://youtube.com ✓ Medium
__Secure-ROLLOUT_TOKEN CPuykLucktiDWBCKzZ6okLmLAximj_KpgbqTAw%3D%3D .youtube.com / 2026-09-21T02:32:45.104Z 66 ✓ ✓ None https://youtube.com Medium APISID OnSMY049lYtHikQy/A5cj2ru6POklKZoFC .google.com / 2027-05-01T01:26:55.647Z 40 High
_gcl_au 1.1.804233229.1768921729 .youtube.com / 2026-04-20T15:08:49.000Z 31 Medium APISID OnSMY049lYtHikQy/A5cj2ru6POklKZoFC .google.co.id / 2027-05-01T01:26:56.717Z 40 High
AEC AaJma5u8FgHY34zVcINRn3aV7kclTK8WLQNF5t6Vx4CV-lCCTfNWLQLU_A .google.com / 2026-04-04T03:15:45.043Z 61 ✓ ✓ Lax Medium GPS 1 .youtube.com / 2026-03-27T03:09:36.573Z 4 ✓ ✓ Medium
APC AfxxVi7ddIpw9dyE7CafpiBRx2fLbINQXwCWfbOjJujb7m4E7VjYkg .doubleclick.net / 2026-05-26T06:48:31.384Z 57 ✓ None https://youtube.com ✓ Medium HSID AOTQRxoGPcLgaWqhk .google.com / 2027-05-01T01:26:55.647Z 21 ✓ High
APISID PbWG-XrqTeJJFiPn/ACQ0hTzgL5lCMuKP5 .google.co.id / 2027-04-29T01:16:47.770Z 40 High HSID AvGoSoPSULrXuoRT9 .google.co.id / 2027-05-01T01:26:56.717Z 21 ✓ High
APISID PbWG-XrqTeJJFiPn/ACQ0hTzgL5lCMuKP5 .youtube.com / 2027-04-29T01:16:46.387Z 40 High IDE AHWqTUmyGzw548Vz7XpBhz59fxCsE3RWJDZPUh3zrwecOjSQ1O8bujmlLDEeiEqCdE8 .doubleclick.net / 2027-04-30T02:35:52.982Z 70 ✓ ✓ None Medium
APISID PbWG-XrqTeJJFiPn/ACQ0hTzgL5lCMuKP5 .google.com / 2027-04-29T01:16:45.016Z 40 High LOGIN_INFO AFmmF2swQwIfXTYVZVbXCVUR4NikS0IeA8v1bJ89WfHBNfr5ipEBGQIgK6RN_6Ubyj7GXQEy9AxZ9tofqAHiELNQ9NP0xRUrZL0:QUQ3MjNmejU3NEZORFNHTk5MM1BBam9mTm9sTGlLVXRSVk1qa3ExU0dkRlFHMjZMSUNwZlZrRFRmcUIxNHZ4S1p5WG5FbkEzSnNhQ3NITVhxSW5NVVBoc3BaV1N1OTFBYXd4cFdMZ2NNcXEwMFY4UEZsVmZLckNWMGtOVUFyRW5DUEdtLU9KX3pyMGZDWUdZdWhzejlIUWwtOUlyOEdNUnN3 .youtube.com / 2027-05-01T02:39:38.516Z 326 ✓ ✓ None Medium

View File

@@ -2,107 +2,134 @@ import yt_dlp
import os import os
import asyncio import asyncio
# Buat folder penampungan sementara jika belum ada
DOWNLOAD_DIR = "downloads" DOWNLOAD_DIR = "downloads"
os.makedirs(DOWNLOAD_DIR, exist_ok=True) os.makedirs(DOWNLOAD_DIR, exist_ok=True)
def generate_netscape_cookies(): def generate_netscape_cookies():
"""
Mengubah data tabel cookie (copy-paste dari tab Application browser)
menjadi format baku Netscape HTTP Cookie agar bisa dibaca yt-dlp.
"""
if not os.path.exists("raw_cookie.txt"): if not os.path.exists("raw_cookie.txt"):
print("⚠️ raw_cookie.txt tidak ditemukan. Lanjut tanpa cookies.")
return False return False
try: try:
with open("raw_cookie.txt", "r", encoding="utf-8") as f: with open("raw_cookie.txt", "r", encoding="utf-8") as f:
lines = f.readlines() lines = f.readlines()
with open("cookies.txt", "w", encoding="utf-8") as f: with open("cookies.txt", "w", encoding="utf-8") as f:
f.write("# Netscape HTTP Cookie File\n") f.write("# Netscape HTTP Cookie File\n")
f.write("# This is a generated file! Do not edit.\n\n") f.write("# This is a generated file! Do not edit.\n\n")
for line in lines: for line in lines:
# Pisahkan kolom berdasarkan tombol Tab (\t) bawaan copy-paste tabel
parts = line.split('\t') parts = line.split('\t')
# Pastikan baris tersebut valid (minimal punya Nama dan Value)
if len(parts) >= 2: if len(parts) >= 2:
name = parts[0].strip() name, value = parts[0].strip(), parts[1].strip()
value = parts[1].strip() if name.lower() == "name" or not name: continue
# Abaikan jika yang ter-copy adalah Header tabelnya ("Name", "Value")
if name.lower() == "name" or not name:
continue
# Format baku Netscape: domain, subdomains, path, secure, expiration, name, value
# Kita paksa domainnya .youtube.com agar yt-dlp bisa membacanya
f.write(f".youtube.com\tTRUE\t/\tTRUE\t2147483647\t{name}\t{value}\n") f.write(f".youtube.com\tTRUE\t/\tTRUE\t2147483647\t{name}\t{value}\n")
print("✅ Cookies berhasil di-generate dari raw_cookie.txt")
return True return True
except Exception as e: except Exception as e:
print(f"Error saat memproses cookies: {e}") print(f"Error parsing cookies: {e}")
return False return False
def fetch_recommendations_sync(video_id: str, limit: int = 30):
ydl_opts_recs = {
'quiet': True,
'extract_flat': True,
'playlistend': limit + 2,
'extractor_args': {'youtube': ['player_client=android,ios']},
}
if os.path.exists("cookies.txt"):
ydl_opts_recs['cookiefile'] = 'cookies.txt'
try:
with yt_dlp.YoutubeDL(ydl_opts_recs) as ydl:
mix_url = f"https://www.youtube.com/watch?v={video_id}&list=RD{video_id}"
info = ydl.extract_info(mix_url, download=False)
recs = []
if 'entries' in info:
for entry in info['entries'][1:]:
if entry and entry.get('id') and entry.get('title'):
recs.append({'id': entry['id'], 'title': entry['title']})
return recs
except Exception as e:
return []
async def get_recommendations(video_id: str):
return await asyncio.to_thread(fetch_recommendations_sync, video_id)
def search_and_download(query_or_url: str): def search_and_download(query_or_url: str):
"""
Fungsi utama untuk mencari dan mengunduh audio dari YouTube.
Berjalan secara sinkronus.
"""
# 1. Konversi cookie setiap kali mau download
has_cookies = generate_netscape_cookies() has_cookies = generate_netscape_cookies()
# 2. Pengaturan yt-dlp
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best/m4a/mp3', # ❌ FFMPEG POSTPROCESSORS DIHAPUS ❌
# Langsung tembak format asli m4a (AAC) agar CPU tidak tersiksa
'format': 'bestaudio[ext=m4a]/bestaudio/best',
'outtmpl': f'{DOWNLOAD_DIR}/%(id)s.%(ext)s', 'outtmpl': f'{DOWNLOAD_DIR}/%(id)s.%(ext)s',
'max_filesize': 45000000, # Batas aman Telegram 45MB 'max_filesize': 45000000,
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '128',
}],
'quiet': True, 'quiet': True,
'noplaylist': True, 'noplaylist': True,
'default_search': 'ytsearch1', 'default_search': 'ytsearch1',
'extractor_args': {'youtube': ['player_client=android,ios']},
'js_runtimes': {'node': {}}, 'js_runtimes': {'node': {}},
# 'extractor_args': {'youtube': ['player_client=android,ios']},
} }
# 3. Sisipkan cookie jika berhasil digenerate
if has_cookies and os.path.exists("cookies.txt"): if has_cookies and os.path.exists("cookies.txt"):
ydl_opts['cookiefile'] = 'cookies.txt' ydl_opts['cookiefile'] = 'cookies.txt'
try: try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl: with yt_dlp.YoutubeDL(ydl_opts) as ydl:
# Mulai proses ekstraksi & download
info = ydl.extract_info(query_or_url, download=True) info = ydl.extract_info(query_or_url, download=True)
# Jika hasil berupa playlist/pencarian, ambil item pertama
if 'entries' in info: if 'entries' in info:
info = info['entries'][0] info = info['entries'][0]
video_id = info.get('id') video_id = info.get('id')
title = info.get('title') title = info.get('title')
duration = info.get('duration') # Ambil ekstensi aslinya (kemungkinan besar m4a)
ext = info.get('ext', 'm4a')
file_path = f"{DOWNLOAD_DIR}/{video_id}.mp3" file_path = f"{DOWNLOAD_DIR}/{video_id}.{ext}"
return { return {
"status": "success", "status": "success",
"video_id": video_id, "video_id": video_id,
"title": title, "title": title,
"duration": duration, "file_path": file_path if os.path.exists(file_path) else None,
"file_path": file_path if os.path.exists(file_path) else None "ext": ext
} }
except Exception as e: except Exception as e:
return {"status": "error", "message": str(e)} return {"status": "error", "message": str(e)}
async def process_youtube_request(query: str): async def process_youtube_request(query: str):
"""
Bungkus (Wrapper) Async agar bot Telegram tidak nge-hang
saat menunggu proses download selesai.
"""
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)