init awal 30 lagu

This commit is contained in:
YOLANDO
2026-03-26 08:58:08 +07:00
parent 4051b0f054
commit 2e2b10890c
5 changed files with 148 additions and 48 deletions

144
bot.py
View File

@@ -11,7 +11,8 @@ from aiogram.client.session.aiohttp import AiohttpSession
from dotenv import load_dotenv
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from yt_engine import process_youtube_request, get_recommendations
# Import fungsi baru search_youtube_list
from yt_engine import process_youtube_request, get_recommendations, search_youtube_list
from db_manager import db
from s3_manager import upload_audio, download_audio, delete_audio
@@ -20,7 +21,9 @@ logger = logging.getLogger(__name__)
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
AUTHORIZED_USER_ID = int(os.getenv("AUTHORIZED_USER_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()}
session = AiohttpSession(timeout=300)
bot = Bot(token=BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=None))
@@ -28,6 +31,10 @@ dp = Dispatcher()
music_queue = asyncio.Queue()
# --- PENYIMPANAN SEMENTARA UNTUK PAGING ---
# Format: { chat_id: {'query': 'judul', 'results': [list_lagu]} }
user_searches = {}
async def queue_worker():
while True:
query, chat_id, msg_id = await music_queue.get()
@@ -40,69 +47,135 @@ async def queue_worker():
class SecurityMiddleware(BaseMiddleware):
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:
logger.warning(f"⚠️ Akses ditolak untuk User ID: {user_id}")
return
return await handler(event, data)
dp.message.middleware(SecurityMiddleware())
dp.callback_query.middleware(SecurityMiddleware())
# Ekstraktor ID YouTube
def extract_video_id(query):
match = re.search(r"(?:v=|\/)([0-9A-Za-z_-]{11}).*", query)
return match.group(1) if match else None
# --- FUNGSI PEMBUAT TOMBOL PAGING ---
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:
# Tampilkan Judul | Nama Channel (Biar tahu itu lagu asli atau cover)
display_text = f"🎵 {rec['title']} | {rec['uploader']}"
short_text = display_text[:55] + "..." if len(display_text) > 55 else display_text
# Menggunakan format callback "play_" agar otomatis masuk antrean download
kb.append([InlineKeyboardButton(text=short_text, callback_data=f"play_{rec['id']}")])
# Tombol Navigasi Next/Prev
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)
# --- COMMANDS ---
@dp.message(Command("start"))
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 Ready!**\nKirim format: `/play <judul lagu>`", parse_mode="Markdown")
@dp.message(Command("play"))
async def cmd_play(message: types.Message):
query = message.text.replace("/play", "").strip()
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")
# Ambil 30 hasil sekaligus
results = await search_youtube_list(query, max_results=30)
if not results:
return await status_msg.edit_text("❌ Lagu tidak ditemukan.")
# Simpan ke memori sementara untuk paging
user_searches[message.chat.id] = {
'query': query,
'results': results
}
# Tampilkan halaman pertama (index 0)
kb = get_search_keyboard(message.chat.id, page=0)
await status_msg.edit_text(
text=f"🔎 Hasil pencarian: **{query}**\n_Pilih lagu untuk diunduh:_",
reply_markup=kb,
parse_mode="Markdown"
)
# --- HANDLER TOMBOL PAGING (NEXT/PREV) ---
@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("Pencarian kadaluarsa. Ketik /play lagi.", show_alert=True)
page = int(callback.data.split("_")[1])
kb = get_search_keyboard(chat_id, page)
if kb:
query = user_searches[chat_id]['query']
await callback.message.edit_text(
text=f"🔎 Hasil pencarian: **{query}**\n_Halaman {page+1}_",
reply_markup=kb,
parse_mode="Markdown"
)
await callback.answer()
# --- HANDLER DOWNLOAD LAGU ---
@dp.callback_query(F.data.startswith("play_"))
async def handle_suggestion_click(callback: CallbackQuery):
video_id = callback.data.split("_", 1)[1]
# Opsional: Jika tidak mau layarnya penuh tombol setelah diklik, hapus tombolnya
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}"
await music_queue.put((query_url, callback.message.chat.id, status_msg.message_id))
await callback.answer("Siapp! Diproses...")
# --- CORE LOGIC (DENGAN SMART CACHE) ---
# --- CORE LOGIC (DOWNLOAD & S3 CACHE) ---
async def process_music_request(query: str, chat_id: int, status_msg_id: int):
local_file = None
total_start_time = time.time()
try:
logger.info(f"▶️ MEMULAI PROSES: {query}")
# 1. SMART CACHE CHECK (CEK DB DULUAN)
video_id = extract_video_id(query)
cached_data = None
if video_id:
t_s3_check = time.time()
cached_data = await db.get_cache(video_id)
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['title']
s3_object_name = cached_data['s3_object_key']
# Bisa saja file lama berekstensi mp3, atau baru berekstensi m4a
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 langsung dari Cache (MinIO)...", chat_id, status_msg_id)
await bot.edit_message_text("⚡ Mengambil dari Cache...", chat_id, status_msg_id)
await download_audio(s3_object_name, local_file)
# 2. JIKA TIDAK ADA DI CACHE, BARU PANGGIL YOUTUBE
if not cached_data:
await bot.edit_message_text("🔍 Mencari dan mengunduh dari YouTube...", chat_id, status_msg_id)
t_yt_start = time.time()
await bot.edit_message_text("🔍 Mengunduh dari YouTube...", chat_id, status_msg_id)
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":
return await bot.edit_message_text(f"❌ Gagal: {yt_result['message']}", chat_id, status_msg_id)
@@ -110,42 +183,29 @@ async def process_music_request(query: str, chat_id: int, status_msg_id: int):
video_id = yt_result["video_id"]
title = yt_result["title"]
local_file = yt_result["file_path"]
# Simpan sesuai ekstensi aslinya (sekarang biasanya m4a)
s3_object_name = f"{video_id}.{yt_result['ext']}"
await bot.edit_message_text("☁️ Mengunggah ke MinIO Server...", chat_id, status_msg_id)
await bot.edit_message_text("☁️ Menyimpan ke Server...", chat_id, status_msg_id)
if local_file and os.path.exists(local_file):
t_s3_up = time.time()
await upload_audio(local_file, 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 file ke Telegram...", chat_id, status_msg_id)
await bot.edit_message_text("📤 Mengirim ke Telegram...", chat_id, status_msg_id)
if local_file and os.path.exists(local_file):
t_tele_start = time.time()
audio = FSInputFile(local_file)
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)
# 4. SUGGEST 5 LAGU (Judul lebih panjang)
t_recs = time.time()
# --- TETAP TAMPILKAN 5 SUGGESTION SETELAH LAGU SELESAI DIPUTAR ---
recs = await get_recommendations(video_id)
logger.info(f"⏱️ Fetch Rekomendasi memakan waktu: {time.time() - t_recs:.2f} detik")
if recs:
kb = []
for rec in recs[:5]:
short_title = rec['title'][:60] + "..." if len(rec['title']) > 60 else rec['title']
short_title = rec['title'][:55] + "..." if len(rec['title']) > 55 else rec['title']
kb.append([InlineKeyboardButton(text=f"▶️ {short_title}", callback_data=f"play_{rec['id']}")])
reply_markup = InlineKeyboardMarkup(inline_keyboard=kb)
await bot.send_message(chat_id, text=f"💡 **Rekomendasi dari:**\n_{title}_", reply_markup=reply_markup, parse_mode="Markdown")
logger.info(f"✅ TOTAL WAKTU E2E: {time.time() - total_start_time:.2f} detik\n{'-'*40}")
except Exception as e:
logger.error(f"Error: {e}")
finally:
@@ -163,7 +223,7 @@ async def main():
scheduler = AsyncIOScheduler()
scheduler.add_job(cleanup_expired_cache, 'cron', hour=3, minute=0)
scheduler.start()
logger.info("🚀 Bot Music Turbo Berjalan!")
logger.info("🚀 Bot Music V3 (Search & Paging) Berjalan!")
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(bot)