From 5e5773aac5c8ac70bf941122a6bd0877ae893849 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 00:17:25 +0100 Subject: [PATCH 01/14] implement distinct title search and selection for premium users --- database/ia_filterdb.py | 38 +++++++++++- plugins/pm_filter.py | 131 +++++++++++++++++++++++++++------------- 2 files changed, 127 insertions(+), 42 deletions(-) diff --git a/database/ia_filterdb.py b/database/ia_filterdb.py index 64f93e7..d0d334d 100644 --- a/database/ia_filterdb.py +++ b/database/ia_filterdb.py @@ -170,4 +170,40 @@ def unpack_new_file_id(new_file_id): decoded.access_hash ) ) - return file_id \ No newline at end of file + return file_id + +async def get_distinct_titles(query): + """Get distinct titles matching the search query""" + query = str(query).strip() + if not query: + raw_pattern = '.' + elif ' ' not in query: + raw_pattern = r'(\b|[\.\+\-_])' + query + r'(\b|[\.\+\-_])' + else: + raw_pattern = query.replace(' ', r'.*[\s\.\+\-_]') + + try: + regex = re.compile(raw_pattern, flags=re.IGNORECASE) + except: + regex = query + + # Get distinct titles from both databases + titles = set() + + # Search in primary database + cursor = collection.find({'file_name': regex}) + for doc in cursor: + # Extract title (remove year and quality info) + title = re.sub(r'\(\d{4}\)|\d{4}|720p|1080p|2160p|HDCAM|HDRip|WebRip|WEBRip', '', doc['file_name']) + title = re.sub(r'\.', ' ', title).strip() + titles.add(title) + + # Search in secondary database if available + if SECOND_FILES_DATABASE_URL: + cursor2 = second_collection.find({'file_name': regex}) + for doc in cursor2: + title = re.sub(r'\(\d{4}\)|\d{4}|720p|1080p|2160p|HDCAM|HDRip|WebRip|WEBRip', '', doc['file_name']) + title = re.sub(r'\.', ' ', title).strip() + titles.add(title) + + return sorted(list(titles)) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index a2ddbe5..776d32e 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -26,7 +26,12 @@ async def pm_search(client, message): return await message.reply_text('PM search was disabled!') if await is_premium(message.from_user.id, client): s = await message.reply(f"⚠️ `{message.text}` searching...") - await auto_filter(client, message, s) + # Get distinct titles first + titles = await get_distinct_titles(message.text) + if titles: + await show_title_selection(client, message, titles, s) + else: + await auto_filter(client, message, s) else: files, n_offset, total = await get_search_results(message.text) btn = [[ @@ -34,12 +39,7 @@ async def pm_search(client, message): ],[ InlineKeyboardButton('🤑 Buy Premium', url=f"https://t.me/{temp.U_NAME}?start=premium") ]] - reply_markup=InlineKeyboardMarkup(btn) - if int(total) != 0: - await message.reply_text(f'🤗 ᴛᴏᴛᴀʟ {total} ʀᴇꜱᴜʟᴛꜱ ꜰᴏᴜɴᴅ ɪɴ ᴛʜɪꜱ ɢʀᴏᴜᴘ 👇\n\nor buy premium subscription', reply_markup=reply_markup) - - - + await message.reply_text(f"I found {total} results for your query. Buy premium to access them.", reply_markup=InlineKeyboardMarkup(btn)) @Client.on_message(filters.group & filters.text & filters.incoming) async def group_search(client, message): @@ -61,36 +61,6 @@ async def group_search(client, message): if message.text.startswith("/"): return - - elif '@admin' in message.text.lower() or '@admins' in message.text.lower(): - if await is_check_admin(client, message.chat.id, message.from_user.id): - return - admins = [] - async for member in client.get_chat_members(chat_id=message.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS): - if not member.user.is_bot: - admins.append(member.user.id) - if member.status == enums.ChatMemberStatus.OWNER: - if message.reply_to_message: - try: - sent_msg = await message.reply_to_message.forward(member.user.id) - await sent_msg.reply_text(f"#Attention\n★ User: {message.from_user.mention}\n★ Group: {message.chat.title}\n\n★ Go to message", disable_web_page_preview=True) - except: - pass - else: - try: - sent_msg = await message.forward(member.user.id) - await sent_msg.reply_text(f"#Attention\n★ User: {message.from_user.mention}\n★ Group: {message.chat.title}\n\n★ Go to message", disable_web_page_preview=True) - except: - pass - hidden_mentions = (f'[\u2064](tg://user?id={user_id})' for user_id in admins) - await message.reply_text('Report sent!' + ''.join(hidden_mentions)) - return - - elif re.findall(r'https?://\S+|www\.\S+|t\.me/\S+|@\w+', message.text): - if await is_check_admin(client, message.chat.id, message.from_user.id): - return - await message.delete() - return await message.reply('Links not allowed here!') elif '#request' in message.text.lower(): if message.from_user.id in ADMINS: @@ -100,7 +70,12 @@ async def group_search(client, message): return else: s = await message.reply(f"⚠️ `{message.text}` searching...") - await auto_filter(client, message, s) + # Get distinct titles first + titles = await get_distinct_titles(message.text) + if titles: + await show_title_selection(client, message, titles, s) + else: + await auto_filter(client, message, s) else: k = await message.reply_text('Auto Filter Off! ❌') await asyncio.sleep(5) @@ -1086,14 +1061,17 @@ async def cb_handler(client: Client, query: CallbackQuery): -async def auto_filter(client, msg, s, spoll=False): +async def auto_filter(client, msg, s, spoll=False, title_search=None): if not spoll: message = msg settings = await get_settings(message.chat.id) - search = re.sub(r"\s+", " ", re.sub(r"[-:\"';!]", " ", message.text)).strip() + if title_search: + search = title_search + else: + search = re.sub(r"\s+", " ", re.sub(r"[-:\"';!]", " ", message.text)).strip() files, offset, total_results = await get_search_results(search) if not files: - if settings["spell_check"]: + if settings["spell_check"] and not title_search: await advantage_spell_chok(message, s) else: await s.edit(f'I cant find {search}') @@ -1187,6 +1165,77 @@ async def auto_filter(client, msg, s, spoll=False): else: cap = f"💭 ʜᴇʏ {message.from_user.mention},\n♻️ ʜᴇʀᴇ ɪ ꜰᴏᴜɴᴅ ꜰᴏʀ ʏᴏᴜʀ sᴇᴀʀᴄʜ {search}..." CAP[key] = cap + +# New function to show title selection +async def show_title_selection(client, message, titles, s): + btn = [] + # Limit to 10 titles per page + for i, title in enumerate(titles[:10]): + btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) + + if len(titles) > 10: + btn.append([InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_0")]) + + await s.edit_text( + f"Found {len(titles)} titles matching your search.\n\nPlease select the exact title:", + reply_markup=InlineKeyboardMarkup(btn) + ) + +# Add new callback handler for title selection +@Client.on_callback_query(filters.regex(r"^title")) +async def title_callback(client, query): + if query.data.startswith("title#"): + _, title = query.data.split("#", 1) + s = await query.message.edit(f"⚠️ Searching for `{title}`...") + await auto_filter(client, query.message, s, title_search=title) + + elif query.data.startswith("title_next_"): + _, offset = query.data.split("_", 2) + offset = int(offset) + search = query.message.text.split("matching your search")[0].strip() + titles = await get_distinct_titles(search) + + btn = [] + # Show next 10 titles + for i, title in enumerate(titles[offset+10:offset+20], start=offset+10): + btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) + + nav_btns = [] + if offset > 0: + nav_btns.append(InlineKeyboardButton(text="⏪ Previous", callback_data=f"title_prev_{offset}")) + + if offset + 20 < len(titles): + nav_btns.append(InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_{offset+10}")) + + if nav_btns: + btn.append(nav_btns) + + await query.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(btn)) + + elif query.data.startswith("title_prev_"): + _, offset = query.data.split("_", 2) + offset = int(offset) + new_offset = max(0, offset - 10) + + search = query.message.text.split("matching your search")[0].strip() + titles = await get_distinct_titles(search) + + btn = [] + # Show previous 10 titles + for i, title in enumerate(titles[new_offset:new_offset+10], start=new_offset): + btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) + + nav_btns = [] + if new_offset > 0: + nav_btns.append(InlineKeyboardButton(text="⏪ Previous", callback_data=f"title_prev_{new_offset}")) + + if new_offset + 10 < len(titles): + nav_btns.append(InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_{new_offset}")) + + if nav_btns: + btn.append(nav_btns) + + await query.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(btn)) del_msg = f"\n\n⚠️ ᴛʜɪs ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴀᴜᴛᴏ ᴅᴇʟᴇᴛᴇ ᴀꜰᴛᴇʀ {get_readable_time(DELETE_TIME)} ᴛᴏ ᴀᴠᴏɪᴅ ᴄᴏᴘʏʀɪɢʜᴛ ɪssᴜᴇs" if settings["auto_delete"] else '' if imdb and imdb.get('poster'): await s.delete() From d716ac897b5972606efc7383af70a1bb468ab416 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 00:43:02 +0100 Subject: [PATCH 02/14] add get_distinct_titles import for enhanced title search functionality --- plugins/pm_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 776d32e..17d63ff 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -12,7 +12,7 @@ from hydrogram import Client, filters, enums from utils import is_premium, get_size, is_subscribed, is_check_admin, get_wish, get_shortlink, get_readable_time, get_poster, temp, get_settings, save_group_settings from database.users_chats_db import db -from database.ia_filterdb import get_search_results,delete_files, db_count_documents, second_db_count_documents +from database.ia_filterdb import get_search_results, delete_files, db_count_documents, second_db_count_documents, get_distinct_titles from plugins.commands import get_grp_stg BUTTONS = {} From 181790da5901f993c0f3a481806d5985ad215944 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 00:48:55 +0100 Subject: [PATCH 03/14] refactor: clean up whitespace and improve readability in pm_filter.py --- plugins/pm_filter.py | 171 ++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 17d63ff..d70a059 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -58,16 +58,16 @@ async def group_search(client, message): ]] await message.reply_text(f'Total {total} results found in this group', reply_markup=InlineKeyboardMarkup(btn)) return - + if message.text.startswith("/"): return - + elif '#request' in message.text.lower(): if message.from_user.id in ADMINS: return await client.send_message(LOG_CHANNEL, f"#Request\n★ User: {message.from_user.mention}\n★ Group: {message.chat.title}\n\n★ Message: {re.sub(r'#request', '', message.text.lower())}") await message.reply_text("Request sent!") - return + return else: s = await message.reply(f"⚠️ `{message.text}` searching...") # Get distinct titles first @@ -146,7 +146,7 @@ async def next_page(bot, query): off_set = None else: off_set = offset - MAX_BTN - + if n_offset == 0: btn.append( [InlineKeyboardButton("« ʙᴀᴄᴋ", callback_data=f"next_{req}_{key}_{off_set}"), @@ -179,7 +179,7 @@ async def languages_(client: Client, query: CallbackQuery): InlineKeyboardButton(text=LANGUAGES[i+1].title(), callback_data=f"lang_search#{LANGUAGES[i+1]}#{key}#{offset}#{req}")] for i in range(0, len(LANGUAGES)-1, 2) ] - btn.append([InlineKeyboardButton(text="⪻ ʙᴀᴄᴋ ᴛᴏ ᴍᴀɪɴ ᴘᴀɢᴇ", callback_data=f"next_{req}_{key}_{offset}")]) + btn.append([InlineKeyboardButton(text="⪻ ʙᴀᴄᴋ ᴛᴏ ᴍᴀɪɴ ᴘᴀɢᴇ", callback_data=f"next_{req}_{key}_{offset}")]) await query.message.edit_text("ɪɴ ᴡʜɪᴄʜ ʟᴀɴɢᴜᴀɢᴇ ᴅᴏ ʏᴏᴜ ᴡᴀɴᴛ, sᴇʟᴇᴄᴛ ʜᴇʀᴇ 👇", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(btn)) @Client.on_callback_query(filters.regex(r"^quality")) @@ -192,7 +192,7 @@ async def quality(client: Client, query: CallbackQuery): InlineKeyboardButton(text=QUALITY[i+1].title(), callback_data=f"qual_search#{QUALITY[i+1]}#{key}#{offset}#{req}")] for i in range(0, len(QUALITY)-1, 2) ] - btn.append([InlineKeyboardButton(text="⪻ ʙᴀᴄᴋ ᴛᴏ ᴍᴀɪɴ ᴘᴀɢᴇ", callback_data=f"next_{req}_{key}_{offset}")]) + btn.append([InlineKeyboardButton(text="⪻ ʙᴀᴄᴋ ᴛᴏ ᴍᴀɪɴ ᴘᴀɢᴇ", callback_data=f"next_{req}_{key}_{offset}")]) await query.message.edit_text("ɪɴ ᴡʜɪᴄʜ ǫᴜᴀʟɪᴛʏ ᴅᴏ ʏᴏᴜ ᴡᴀɴᴛ, sᴇʟᴇᴄᴛ ʜᴇʀᴇ 👇", disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(btn)) @Client.on_callback_query(filters.regex(r"^lang_search")) @@ -205,7 +205,7 @@ async def filter_languages_cb_handler(client: Client, query: CallbackQuery): cap = CAP.get(key) if not search: await query.answer(f"Hello {query.from_user.first_name},\nSend New Request Again!", show_alert=True) - return + return files, l_offset, total_results = await get_search_results(search, lang=lang) if not files: @@ -236,7 +236,7 @@ async def filter_languages_cb_handler(client: Client, query: CallbackQuery): [InlineKeyboardButton("♻️ sᴇɴᴅ ᴀʟʟ ♻️", callback_data=f"send_all#{key}#{req}"), InlineKeyboardButton("🔍 ǫᴜᴀʟɪᴛʏ", callback_data=f"quality#{key}#{req}#{offset}")] ) - + if l_offset != "": btn.append( [InlineKeyboardButton(text=f"1/{math.ceil(int(total_results) / MAX_BTN)}", callback_data="buttons"), @@ -350,7 +350,7 @@ async def quality_search(client: Client, query: CallbackQuery): else: btn.insert(0, [InlineKeyboardButton("♻️ sᴇɴᴅ ᴀʟʟ ♻️", callback_data=f"send_all#{key}#{req}")] - ) + ) if l_offset != "": btn.append( [InlineKeyboardButton(text=f"1/{math.ceil(int(total_results) / MAX_BTN)}", callback_data="buttons"), @@ -465,7 +465,7 @@ async def cb_handler(client: Client, query: CallbackQuery): await query.message.reply_to_message.delete() except: pass - + if query.data.startswith("file"): ident, file_id = query.data.split("#") try: @@ -489,7 +489,7 @@ async def cb_handler(client: Client, query: CallbackQuery): return await query.answer(f"Only for premium users, use /plan for details", show_alert=True) await query.answer(url=f"https://t.me/{temp.U_NAME}?start=all_{group_id}_{key}") await query.message.delete() - + elif query.data.startswith("stream"): file_id = query.data.split('#', 1)[1] if not await is_premium(query.from_user.id, client): @@ -507,8 +507,8 @@ async def cb_handler(client: Client, query: CallbackQuery): await query.edit_message_reply_markup( reply_markup=reply_markup ) - - + + elif query.data.startswith("checksub"): ident, mc = query.data.split("#") settings = await get_settings(int(mc.split("_", 2)[1])) @@ -597,7 +597,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InputMediaPhoto(random.choice(PICS), caption=script.START_TXT.format(query.from_user.mention, get_wish())), reply_markup=reply_markup ) - + elif query.data == "about": buttons = [[ InlineKeyboardButton('📊 sᴛᴀᴛᴜs 📊', callback_data='stats'), @@ -637,7 +637,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InputMediaPhoto(random.choice(PICS), caption=script.STATUS_TXT.format(users, prm, chats, used_data_db_size, files, used_files_db_size, secnd_files, secnd_files_db_used_size, uptime)), reply_markup=InlineKeyboardMarkup(buttons) ) - + elif query.data == 'premium': if not IS_PREMIUM: return await query.answer('Premium feature was disabled by admin', show_alert=True) @@ -658,7 +658,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InputMediaPhoto(random.choice(PICS), caption=script.MY_OWNER_TXT), reply_markup=reply_markup ) - + elif query.data == "help": buttons = [[ InlineKeyboardButton('User Command', callback_data='user_command'), @@ -681,7 +681,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InputMediaPhoto(random.choice(PICS), caption=script.USER_COMMAND_TXT), reply_markup=reply_markup ) - + elif query.data == "admin_command": if query.from_user.id not in ADMINS: return await query.answer("ADMINS Only!", show_alert=True) @@ -703,7 +703,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InputMediaPhoto(random.choice(PICS), caption=script.SOURCE_TXT), reply_markup=reply_markup ) - + elif query.data.startswith("bool_setgs"): ident, set_type, status, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -718,7 +718,7 @@ async def cb_handler(client: Client, query: CallbackQuery): btn = await get_grp_stg(int(grp_id)) await query.message.edit_reply_markup(InlineKeyboardMarkup(btn)) - + elif query.data.startswith("imdb_setgs"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -799,7 +799,7 @@ async def cb_handler(client: Client, query: CallbackQuery): ]] await query.message.edit('Successfully changed Welcome to default', reply_markup=InlineKeyboardMarkup(btn)) - + elif query.data.startswith("tutorial_setgs"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -814,7 +814,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InlineKeyboardButton('Back', callback_data=f'back_setgs#{grp_id}') ]] await query.message.edit(f'Select you want option\n\nCurrent tutorial link:\n{settings["tutorial"]}', reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True) - + elif query.data.startswith("set_tutorial"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -840,7 +840,7 @@ async def cb_handler(client: Client, query: CallbackQuery): ]] await query.message.edit('Successfully changed tutorial link to default', reply_markup=InlineKeyboardMarkup(btn)) - + elif query.data.startswith("shortlink_setgs"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -855,7 +855,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InlineKeyboardButton('Back', callback_data=f'back_setgs#{grp_id}') ]] await query.message.edit(f'Select you want option\n\nCurrent shortlink:\n{settings["url"]} - {settings["api"]}', reply_markup=InlineKeyboardMarkup(btn)) - + elif query.data.startswith("set_shortlink"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -900,8 +900,8 @@ async def cb_handler(client: Client, query: CallbackQuery): InlineKeyboardButton('Back', callback_data=f'back_setgs#{grp_id}') ]] await query.message.edit(f'Select you want option\n\nCurrent caption:\n{settings["caption"]}', reply_markup=InlineKeyboardMarkup(btn)) - - + + elif query.data.startswith("set_caption"): _, grp_id = query.data.split("#") userid = query.from_user.id if query.from_user else None @@ -963,15 +963,15 @@ async def cb_handler(client: Client, query: CallbackQuery): await query.message.edit('Deleting...') deleted = await delete_files(query_) await query.message.edit(f'Deleted {deleted} files in your database in your query {query_}') - + elif query.data.startswith("send_all"): ident, key, req = query.data.split("#") if int(req) != query.from_user.id: - return await query.answer(f"Hello {query.from_user.first_name},\nDon't Click Other Results!", show_alert=True) + return await query.answer(f"Hello {query.from_user.first_name},\nDon't Click Other Results!", show_alert=True) files = temp.FILES.get(key) if not files: await query.answer(f"Hello {query.from_user.first_name},\nSend New Request Again!", show_alert=True) - return + return await query.answer(url=f"https://t.me/{temp.U_NAME}?start=all_{query.message.chat.id}_{key}") elif query.data == "unmute_all_members": @@ -1094,7 +1094,7 @@ async def auto_filter(client, msg, s, spoll=False, title_search=None): InlineKeyboardButton(text=f"{get_size(file['file_size'])} - {file['file_name']}", callback_data=f'file#{file["_id"]}') ] for file in files - ] + ] if offset != "": if settings['shortlink'] and not await is_premium(message.from_user.id, client): btn.insert(0, @@ -1166,16 +1166,58 @@ async def auto_filter(client, msg, s, spoll=False, title_search=None): cap = f"💭 ʜᴇʏ {message.from_user.mention},\n♻️ ʜᴇʀᴇ ɪ ꜰᴏᴜɴᴅ ꜰᴏʀ ʏᴏᴜʀ sᴇᴀʀᴄʜ {search}..." CAP[key] = cap + del_msg = f"\n\n⚠️ ᴛʜɪs ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴀᴜᴛᴏ ᴅᴇʟᴇᴛᴇ ᴀꜰᴛᴇʀ {get_readable_time(DELETE_TIME)} ᴛᴏ ᴀᴠᴏɪᴅ ᴄᴏᴘʏʀɪɢʜᴛ ɪssᴜᴇs" if settings["auto_delete"] else '' + if imdb and imdb.get('poster'): + await s.delete() + try: + k = await message.reply_photo(photo=imdb.get('poster'), caption=cap[:1024] + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), parse_mode=enums.ParseMode.HTML, quote=True) + if settings["auto_delete"]: + await asyncio.sleep(DELETE_TIME) + await k.delete() + try: + await message.delete() + except: + pass + except (MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty): + pic = imdb.get('poster') + poster = pic.replace('.jpg', "._V1_UX360.jpg") + k = await message.reply_photo(photo=poster, caption=cap[:1024] + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), parse_mode=enums.ParseMode.HTML, quote=True) + if settings["auto_delete"]: + await asyncio.sleep(DELETE_TIME) + await k.delete() + try: + await message.delete() + except: + pass + except Exception as e: + k = await message.reply_text(cap + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True, parse_mode=enums.ParseMode.HTML, quote=True) + if settings["auto_delete"]: + await asyncio.sleep(DELETE_TIME) + await k.delete() + try: + await message.delete() + except: + pass + else: + k = await s.edit_text(cap + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True, parse_mode=enums.ParseMode.HTML) + if settings["auto_delete"]: + await asyncio.sleep(DELETE_TIME) + await k.delete() + try: + await message.delete() + except: + pass + # New function to show title selection async def show_title_selection(client, message, titles, s): btn = [] # Limit to 10 titles per page for i, title in enumerate(titles[:10]): btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) - + if len(titles) > 10: btn.append([InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_0")]) - + await s.edit_text( f"Found {len(titles)} titles matching your search.\n\nPlease select the exact title:", reply_markup=InlineKeyboardMarkup(btn) @@ -1188,95 +1230,54 @@ async def title_callback(client, query): _, title = query.data.split("#", 1) s = await query.message.edit(f"⚠️ Searching for `{title}`...") await auto_filter(client, query.message, s, title_search=title) - + elif query.data.startswith("title_next_"): _, offset = query.data.split("_", 2) offset = int(offset) search = query.message.text.split("matching your search")[0].strip() titles = await get_distinct_titles(search) - + btn = [] # Show next 10 titles for i, title in enumerate(titles[offset+10:offset+20], start=offset+10): btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) - + nav_btns = [] if offset > 0: nav_btns.append(InlineKeyboardButton(text="⏪ Previous", callback_data=f"title_prev_{offset}")) - + if offset + 20 < len(titles): nav_btns.append(InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_{offset+10}")) - + if nav_btns: btn.append(nav_btns) - + await query.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(btn)) - + elif query.data.startswith("title_prev_"): _, offset = query.data.split("_", 2) offset = int(offset) new_offset = max(0, offset - 10) - + search = query.message.text.split("matching your search")[0].strip() titles = await get_distinct_titles(search) - + btn = [] # Show previous 10 titles for i, title in enumerate(titles[new_offset:new_offset+10], start=new_offset): btn.append([InlineKeyboardButton(text=f"{i+1}. {title}", callback_data=f"title#{title}")]) - + nav_btns = [] if new_offset > 0: nav_btns.append(InlineKeyboardButton(text="⏪ Previous", callback_data=f"title_prev_{new_offset}")) - + if new_offset + 10 < len(titles): nav_btns.append(InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_{new_offset}")) - + if nav_btns: btn.append(nav_btns) - + await query.message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(btn)) - del_msg = f"\n\n⚠️ ᴛʜɪs ᴍᴇssᴀɢᴇ ᴡɪʟʟ ʙᴇ ᴀᴜᴛᴏ ᴅᴇʟᴇᴛᴇ ᴀꜰᴛᴇʀ {get_readable_time(DELETE_TIME)} ᴛᴏ ᴀᴠᴏɪᴅ ᴄᴏᴘʏʀɪɢʜᴛ ɪssᴜᴇs" if settings["auto_delete"] else '' - if imdb and imdb.get('poster'): - await s.delete() - try: - k = await message.reply_photo(photo=imdb.get('poster'), caption=cap[:1024] + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), parse_mode=enums.ParseMode.HTML, quote=True) - if settings["auto_delete"]: - await asyncio.sleep(DELETE_TIME) - await k.delete() - try: - await message.delete() - except: - pass - except (MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty): - pic = imdb.get('poster') - poster = pic.replace('.jpg', "._V1_UX360.jpg") - k = await message.reply_photo(photo=poster, caption=cap[:1024] + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), parse_mode=enums.ParseMode.HTML, quote=True) - if settings["auto_delete"]: - await asyncio.sleep(DELETE_TIME) - await k.delete() - try: - await message.delete() - except: - pass - except Exception as e: - k = await message.reply_text(cap + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True, parse_mode=enums.ParseMode.HTML, quote=True) - if settings["auto_delete"]: - await asyncio.sleep(DELETE_TIME) - await k.delete() - try: - await message.delete() - except: - pass - else: - k = await s.edit_text(cap + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True, parse_mode=enums.ParseMode.HTML) - if settings["auto_delete"]: - await asyncio.sleep(DELETE_TIME) - await k.delete() - try: - await message.delete() - except: - pass async def advantage_spell_chok(message, s): search = message.text From e8f45c4e933eade8c4b7d57c63085f79cafd10c0 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 01:07:52 +0100 Subject: [PATCH 04/14] Search refine and some fixes --- .../__pycache__/ia_filterdb.cpython-313.pyc | Bin 0 -> 11748 bytes database/ia_filterdb.py | 113 ++++++++++++++---- plugins/pm_filter.py | 23 +++- test_title_cleaning.py | 90 ++++++++++++++ 4 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 database/__pycache__/ia_filterdb.cpython-313.pyc create mode 100644 test_title_cleaning.py diff --git a/database/__pycache__/ia_filterdb.cpython-313.pyc b/database/__pycache__/ia_filterdb.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a745215167ea9f9b3069b90f6dfebede5141b1a1 GIT binary patch literal 11748 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 total_results: - next_offset = '' + next_offset = '' return files, next_offset, total_results async def delete_files(query): @@ -120,24 +120,24 @@ async def delete_files(query): raw_pattern = r'(\b|[\.\+\-_])' + query + r'(\b|[\.\+\-_])' else: raw_pattern = query.replace(' ', r'.*[\s\.\+\-_]') - + try: regex = re.compile(raw_pattern, flags=re.IGNORECASE) except: regex = query - + filter = {'file_name': regex} - + result1 = collection.delete_many(filter) - + result2 = None if SECOND_FILES_DATABASE_URL: result2 = second_collection.delete_many(filter) - + total_deleted = result1.deleted_count if result2: total_deleted += result2.deleted_count - + return total_deleted async def get_file_details(query): @@ -172,6 +172,69 @@ def unpack_new_file_id(new_file_id): ) return file_id +def clean_title(filename): + """Clean filename to extract just the movie/show title""" + title = filename + + # Remove file extensions + title = re.sub(r'\.(mkv|mp4|avi|mov|wmv|flv|webm|m4v|3gp|ts|m2ts)$', '', title, flags=re.IGNORECASE) + + # Remove quality indicators + quality_patterns = [ + r'\b(480p|720p|1080p|1440p|2160p|4k|8k)\b', + r'\b(HD|FHD|UHD|QHD)\b', + r'\b(CAM|TS|TC|SCR|DVDSCR|R5|DVDRip|BDRip|BRRip)\b', + r'\b(WEBRip|WEB-DL|WEB|HDRip|BluRay|Bluray|BDRemux)\b', + r'\b(HDCAM|HDTS|HDTC|PreDVDRip)\b' + ] + + # Remove codec and format info + codec_patterns = [ + r'\b(x264|x265|H264|H265|HEVC|AVC|XviD|DivX)\b', + r'\b(AAC|AC3|DTS|MP3|FLAC|Atmos|TrueHD)\b', + r'\b(10bit|8bit|HDR|SDR|Dolby|Vision)\b' + ] + + # Remove source and release info + source_patterns = [ + r'\b(AMZN|NF|HULU|DSNP|HBO|MAX|ATVP|PCOK|PMTP)\b', + r'\b(Netflix|Amazon|Disney|Hotstar|Prime)\b', + r'\b(REPACK|PROPER|REAL|RETAIL|EXTENDED|UNCUT|DC|IMAX)\b', + r'\b(MULTI|DUAL|DUBBED|SUBBED|SUB|DUB)\b' + ] + + # Remove language indicators + language_patterns = [ + r'\b(Hindi|English|Tamil|Telugu|Malayalam|Kannada|Bengali|Punjabi|Gujarati|Marathi|Urdu)\b', + r'\b(HINDI|ENGLISH|TAMIL|TELUGU|MALAYALAM|KANNADA|BENGALI|PUNJABI|GUJARATI|MARATHI|URDU)\b', + r'\b(ORG|ORIGINAL|AUDIO)\b' + ] + + # Remove years in parentheses and standalone + title = re.sub(r'\(\d{4}\)', '', title) + title = re.sub(r'\b(19|20)\d{2}\b', '', title) + + # Remove season/episode info + title = re.sub(r'\b(S\d{1,2}|Season\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) + title = re.sub(r'\b(E\d{1,2}|Episode\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) + title = re.sub(r'\b(S\d{1,2}E\d{1,2})\b', '', title, flags=re.IGNORECASE) + + # Apply all cleaning patterns + all_patterns = quality_patterns + codec_patterns + source_patterns + language_patterns + for pattern in all_patterns: + title = re.sub(pattern, '', title, flags=re.IGNORECASE) + + # Remove common separators and clean up + title = re.sub(r'[\.\-_\[\]\(\)]+', ' ', title) + title = re.sub(r'\s+', ' ', title) # Multiple spaces to single space + title = title.strip() + + # Remove common prefixes/suffixes + title = re.sub(r'^(www\.|download|free|movie|film)\s*', '', title, flags=re.IGNORECASE) + title = re.sub(r'\s*(download|free|movie|film)$', '', title, flags=re.IGNORECASE) + + return title + async def get_distinct_titles(query): """Get distinct titles matching the search query""" query = str(query).strip() @@ -181,7 +244,7 @@ async def get_distinct_titles(query): raw_pattern = r'(\b|[\.\+\-_])' + query + r'(\b|[\.\+\-_])' else: raw_pattern = query.replace(' ', r'.*[\s\.\+\-_]') - + try: regex = re.compile(raw_pattern, flags=re.IGNORECASE) except: @@ -189,21 +252,27 @@ async def get_distinct_titles(query): # Get distinct titles from both databases titles = set() - + # Search in primary database cursor = collection.find({'file_name': regex}) for doc in cursor: - # Extract title (remove year and quality info) - title = re.sub(r'\(\d{4}\)|\d{4}|720p|1080p|2160p|HDCAM|HDRip|WebRip|WEBRip', '', doc['file_name']) - title = re.sub(r'\.', ' ', title).strip() - titles.add(title) - + # Clean the filename to extract just the title + clean_title_text = clean_title(doc['file_name']) + if clean_title_text and len(clean_title_text.strip()) > 2: # Only add non-empty titles + titles.add(clean_title_text) + # Search in secondary database if available if SECOND_FILES_DATABASE_URL: cursor2 = second_collection.find({'file_name': regex}) for doc in cursor2: - title = re.sub(r'\(\d{4}\)|\d{4}|720p|1080p|2160p|HDCAM|HDRip|WebRip|WEBRip', '', doc['file_name']) - title = re.sub(r'\.', ' ', title).strip() - titles.add(title) - - return sorted(list(titles)) + clean_title_text = clean_title(doc['file_name']) + if clean_title_text and len(clean_title_text.strip()) > 2: # Only add non-empty titles + titles.add(clean_title_text) + + # Filter out very short or meaningless titles + filtered_titles = [] + for title in titles: + if len(title.strip()) > 2 and not title.lower() in ['the', 'and', 'or', 'of', 'in', 'on', 'at', 'to', 'for']: + filtered_titles.append(title.strip()) + + return sorted(list(set(filtered_titles))) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index d70a059..6aca02e 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -1218,10 +1218,25 @@ async def show_title_selection(client, message, titles, s): if len(titles) > 10: btn.append([InlineKeyboardButton(text="Next ⏩", callback_data=f"title_next_0")]) - await s.edit_text( - f"Found {len(titles)} titles matching your search.\n\nPlease select the exact title:", - reply_markup=InlineKeyboardMarkup(btn) - ) + try: + await s.edit_text( + f"🎬 Found {len(titles)} titles matching your search.\n\n📝 Please select the exact title:", + reply_markup=InlineKeyboardMarkup(btn) + ) + except Exception as e: + # If edit fails (e.g., MESSAGE_NOT_MODIFIED), try to send a new message + try: + await s.delete() + await message.reply_text( + f"🎬 Found {len(titles)} titles matching your search.\n\n📝 Please select the exact title:", + reply_markup=InlineKeyboardMarkup(btn) + ) + except: + # If all else fails, just send without deleting + await message.reply_text( + f"🎬 Found {len(titles)} titles matching your search.\n\n📝 Please select the exact title:", + reply_markup=InlineKeyboardMarkup(btn) + ) # Add new callback handler for title selection @Client.on_callback_query(filters.regex(r"^title")) diff --git a/test_title_cleaning.py b/test_title_cleaning.py new file mode 100644 index 0000000..b6b42ee --- /dev/null +++ b/test_title_cleaning.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +""" +Test script to demonstrate the enhanced title cleaning functionality +""" + +import re + +def clean_title(filename): + """Clean filename to extract just the movie/show title""" + title = filename + + # Remove file extensions + title = re.sub(r'\.(mkv|mp4|avi|mov|wmv|flv|webm|m4v|3gp|ts|m2ts)$', '', title, flags=re.IGNORECASE) + + # Remove quality indicators + quality_patterns = [ + r'\b(480p|720p|1080p|1440p|2160p|4k|8k)\b', + r'\b(HD|FHD|UHD|QHD)\b', + r'\b(CAM|TS|TC|SCR|DVDSCR|R5|DVDRip|BDRip|BRRip)\b', + r'\b(WEBRip|WEB-DL|WEB|HDRip|BluRay|Bluray|BDRemux)\b', + r'\b(HDCAM|HDTS|HDTC|PreDVDRip)\b' + ] + + # Remove codec and format info + codec_patterns = [ + r'\b(x264|x265|H264|H265|HEVC|AVC|XviD|DivX)\b', + r'\b(AAC|AC3|DTS|MP3|FLAC|Atmos|TrueHD)\b', + r'\b(10bit|8bit|HDR|SDR|Dolby|Vision)\b' + ] + + # Remove source and release info + source_patterns = [ + r'\b(AMZN|NF|HULU|DSNP|HBO|MAX|ATVP|PCOK|PMTP)\b', + r'\b(Netflix|Amazon|Disney|Hotstar|Prime)\b', + r'\b(REPACK|PROPER|REAL|RETAIL|EXTENDED|UNCUT|DC|IMAX)\b', + r'\b(MULTI|DUAL|DUBBED|SUBBED|SUB|DUB)\b' + ] + + # Remove language indicators + language_patterns = [ + r'\b(Hindi|English|Tamil|Telugu|Malayalam|Kannada|Bengali|Punjabi|Gujarati|Marathi|Urdu)\b', + r'\b(HINDI|ENGLISH|TAMIL|TELUGU|MALAYALAM|KANNADA|BENGALI|PUNJABI|GUJARATI|MARATHI|URDU)\b', + r'\b(ORG|ORIGINAL|AUDIO)\b' + ] + + # Remove years in parentheses and standalone + title = re.sub(r'\(\d{4}\)', '', title) + title = re.sub(r'\b(19|20)\d{2}\b', '', title) + + # Remove season/episode info + title = re.sub(r'\b(S\d{1,2}|Season\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) + title = re.sub(r'\b(E\d{1,2}|Episode\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) + title = re.sub(r'\b(S\d{1,2}E\d{1,2})\b', '', title, flags=re.IGNORECASE) + + # Apply all cleaning patterns + all_patterns = quality_patterns + codec_patterns + source_patterns + language_patterns + for pattern in all_patterns: + title = re.sub(pattern, '', title, flags=re.IGNORECASE) + + # Remove common separators and clean up + title = re.sub(r'[\.\-_\[\]\(\)]+', ' ', title) + title = re.sub(r'\s+', ' ', title) # Multiple spaces to single space + title = title.strip() + + # Remove common prefixes/suffixes + title = re.sub(r'^(www\.|download|free|movie|film)\s*', '', title, flags=re.IGNORECASE) + title = re.sub(r'\s*(download|free|movie|film)$', '', title, flags=re.IGNORECASE) + + return title + +# Test cases based on your examples +test_files = [ + "Avengers: Endgame 1080P Bluray HEVC", + "Avengers Endgame 720P WEB HEVC", + "Avengers: Infinity 1080P WEB HEVC", + "Avengers Triology 480P Bluray HEVC", + "Avengers.Infinity.War.2018.1080p.BluRay.x264-SPARKS", + "The.Matrix.1999.720p.BDRip.x264.AAC-YTS", + "Spider-Man No Way Home (2021) 1080p WEBRip x265 HEVC 10bit AAC 5.1", + "John.Wick.Chapter.4.2023.2160p.4K.WEB-DL.x265.10bit.HDR.DDP5.1.Atmos", + "Game.of.Thrones.S08E06.The.Iron.Throne.1080p.AMZN.WEB-DL.DDP5.1.H.264" +] + +print("Title Cleaning Test Results:") +print("=" * 60) +for filename in test_files: + cleaned = clean_title(filename) + print(f"Original: {filename}") + print(f"Cleaned: {cleaned}") + print("-" * 60) From ac3e8e0018047d2ad15164e593dd0e707736e5ed Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 01:31:46 +0100 Subject: [PATCH 05/14] refactor: enhance title cleaning logic to improve accuracy and handle edge cases --- database/ia_filterdb.py | 88 ++++++++++++----------------- test_title_cleaning.py | 121 ++++++++++++++++++++-------------------- 2 files changed, 96 insertions(+), 113 deletions(-) diff --git a/database/ia_filterdb.py b/database/ia_filterdb.py index a0a943a..f5f9b03 100644 --- a/database/ia_filterdb.py +++ b/database/ia_filterdb.py @@ -176,62 +176,46 @@ def clean_title(filename): """Clean filename to extract just the movie/show title""" title = filename - # Remove file extensions + # Remove file extensions first title = re.sub(r'\.(mkv|mp4|avi|mov|wmv|flv|webm|m4v|3gp|ts|m2ts)$', '', title, flags=re.IGNORECASE) - # Remove quality indicators - quality_patterns = [ - r'\b(480p|720p|1080p|1440p|2160p|4k|8k)\b', - r'\b(HD|FHD|UHD|QHD)\b', - r'\b(CAM|TS|TC|SCR|DVDSCR|R5|DVDRip|BDRip|BRRip)\b', - r'\b(WEBRip|WEB-DL|WEB|HDRip|BluRay|Bluray|BDRemux)\b', - r'\b(HDCAM|HDTS|HDTC|PreDVDRip)\b' - ] - - # Remove codec and format info - codec_patterns = [ - r'\b(x264|x265|H264|H265|HEVC|AVC|XviD|DivX)\b', - r'\b(AAC|AC3|DTS|MP3|FLAC|Atmos|TrueHD)\b', - r'\b(10bit|8bit|HDR|SDR|Dolby|Vision)\b' - ] - - # Remove source and release info - source_patterns = [ - r'\b(AMZN|NF|HULU|DSNP|HBO|MAX|ATVP|PCOK|PMTP)\b', - r'\b(Netflix|Amazon|Disney|Hotstar|Prime)\b', - r'\b(REPACK|PROPER|REAL|RETAIL|EXTENDED|UNCUT|DC|IMAX)\b', - r'\b(MULTI|DUAL|DUBBED|SUBBED|SUB|DUB)\b' - ] - - # Remove language indicators - language_patterns = [ - r'\b(Hindi|English|Tamil|Telugu|Malayalam|Kannada|Bengali|Punjabi|Gujarati|Marathi|Urdu)\b', - r'\b(HINDI|ENGLISH|TAMIL|TELUGU|MALAYALAM|KANNADA|BENGALI|PUNJABI|GUJARATI|MARATHI|URDU)\b', - r'\b(ORG|ORIGINAL|AUDIO)\b' - ] - - # Remove years in parentheses and standalone - title = re.sub(r'\(\d{4}\)', '', title) - title = re.sub(r'\b(19|20)\d{2}\b', '', title) - - # Remove season/episode info - title = re.sub(r'\b(S\d{1,2}|Season\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) - title = re.sub(r'\b(E\d{1,2}|Episode\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) - title = re.sub(r'\b(S\d{1,2}E\d{1,2})\b', '', title, flags=re.IGNORECASE) - - # Apply all cleaning patterns - all_patterns = quality_patterns + codec_patterns + source_patterns + language_patterns - for pattern in all_patterns: - title = re.sub(pattern, '', title, flags=re.IGNORECASE) - - # Remove common separators and clean up - title = re.sub(r'[\.\-_\[\]\(\)]+', ' ', title) - title = re.sub(r'\s+', ' ', title) # Multiple spaces to single space + # Strategy: Extract title from beginning until we hit dates or season markers + # Most titles are clean alphanumeric at the start, noise comes after + + # Find where the title likely ends by looking for: + # 1. Year patterns (1900-2099) + # 2. Season/Episode patterns (S01, S1, Season 1, etc.) + # 3. Quality indicators that commonly appear after titles + + # Look for year patterns (movies): "Title.2023" or "Title (2023)" or "Title 2023" + year_match = re.search(r'[\.\s\-_\(]+(19|20)\d{2}[\.\s\-_\)]', title) + if year_match: + title = title[:year_match.start()] + + # Look for season/episode patterns (series): "Title.S01E02" or "Title S1 E1" + season_match = re.search(r'[\.\s\-_]+(S\d{1,2}|Season\s*\d{1,2})', title, flags=re.IGNORECASE) + if season_match: + title = title[:season_match.start()] + + # Look for common quality indicators that appear after titles + quality_match = re.search(r'[\.\s\-_]+(480p|720p|1080p|2160p|4k|HD|FHD|UHD|BluRay|WEB|HDCAM|DVDRip|BDRip|WEBRip)', title, flags=re.IGNORECASE) + if quality_match: + title = title[:quality_match.start()] + + # Convert common separators to spaces + title = re.sub(r'[\.\-_]+', ' ', title) + + # Clean up multiple spaces + title = re.sub(r'\s+', ' ', title) + + # Remove leading/trailing whitespace title = title.strip() - # Remove common prefixes/suffixes - title = re.sub(r'^(www\.|download|free|movie|film)\s*', '', title, flags=re.IGNORECASE) - title = re.sub(r'\s*(download|free|movie|film)$', '', title, flags=re.IGNORECASE) + # Remove common prefixes that might appear at the start + title = re.sub(r'^(www\.|download\s+|free\s+)', '', title, flags=re.IGNORECASE) + + # Handle special cases where title might have colons (like "Avengers: Endgame") + # Keep colons as they're part of legitimate titles return title diff --git a/test_title_cleaning.py b/test_title_cleaning.py index b6b42ee..2eca5f0 100644 --- a/test_title_cleaning.py +++ b/test_title_cleaning.py @@ -8,77 +8,76 @@ def clean_title(filename): """Clean filename to extract just the movie/show title""" title = filename - - # Remove file extensions + + # Remove file extensions first title = re.sub(r'\.(mkv|mp4|avi|mov|wmv|flv|webm|m4v|3gp|ts|m2ts)$', '', title, flags=re.IGNORECASE) - - # Remove quality indicators - quality_patterns = [ - r'\b(480p|720p|1080p|1440p|2160p|4k|8k)\b', - r'\b(HD|FHD|UHD|QHD)\b', - r'\b(CAM|TS|TC|SCR|DVDSCR|R5|DVDRip|BDRip|BRRip)\b', - r'\b(WEBRip|WEB-DL|WEB|HDRip|BluRay|Bluray|BDRemux)\b', - r'\b(HDCAM|HDTS|HDTC|PreDVDRip)\b' - ] - - # Remove codec and format info - codec_patterns = [ - r'\b(x264|x265|H264|H265|HEVC|AVC|XviD|DivX)\b', - r'\b(AAC|AC3|DTS|MP3|FLAC|Atmos|TrueHD)\b', - r'\b(10bit|8bit|HDR|SDR|Dolby|Vision)\b' - ] - - # Remove source and release info - source_patterns = [ - r'\b(AMZN|NF|HULU|DSNP|HBO|MAX|ATVP|PCOK|PMTP)\b', - r'\b(Netflix|Amazon|Disney|Hotstar|Prime)\b', - r'\b(REPACK|PROPER|REAL|RETAIL|EXTENDED|UNCUT|DC|IMAX)\b', - r'\b(MULTI|DUAL|DUBBED|SUBBED|SUB|DUB)\b' - ] - - # Remove language indicators - language_patterns = [ - r'\b(Hindi|English|Tamil|Telugu|Malayalam|Kannada|Bengali|Punjabi|Gujarati|Marathi|Urdu)\b', - r'\b(HINDI|ENGLISH|TAMIL|TELUGU|MALAYALAM|KANNADA|BENGALI|PUNJABI|GUJARATI|MARATHI|URDU)\b', - r'\b(ORG|ORIGINAL|AUDIO)\b' - ] - - # Remove years in parentheses and standalone - title = re.sub(r'\(\d{4}\)', '', title) - title = re.sub(r'\b(19|20)\d{2}\b', '', title) - - # Remove season/episode info - title = re.sub(r'\b(S\d{1,2}|Season\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) - title = re.sub(r'\b(E\d{1,2}|Episode\s*\d{1,2})\b', '', title, flags=re.IGNORECASE) - title = re.sub(r'\b(S\d{1,2}E\d{1,2})\b', '', title, flags=re.IGNORECASE) - - # Apply all cleaning patterns - all_patterns = quality_patterns + codec_patterns + source_patterns + language_patterns - for pattern in all_patterns: - title = re.sub(pattern, '', title, flags=re.IGNORECASE) - - # Remove common separators and clean up - title = re.sub(r'[\.\-_\[\]\(\)]+', ' ', title) - title = re.sub(r'\s+', ' ', title) # Multiple spaces to single space + + # Strategy: Extract title from beginning until we hit dates or season markers + # Most titles are clean alphanumeric at the start, noise comes after + + # Find where the title likely ends by looking for: + # 1. Year patterns (1900-2099) + # 2. Season/Episode patterns (S01, S1, Season 1, etc.) + # 3. Quality indicators that commonly appear after titles + + # Look for year patterns (movies): "Title.2023" or "Title (2023)" or "Title 2023" + year_match = re.search(r'[\.\s\-_\(]+(19|20)\d{2}[\.\s\-_\)]', title) + if year_match: + title = title[:year_match.start()] + + # Look for season/episode patterns (series): "Title.S01E02" or "Title S1 E1" + season_match = re.search(r'[\.\s\-_]+(S\d{1,2}|Season\s*\d{1,2})', title, flags=re.IGNORECASE) + if season_match: + title = title[:season_match.start()] + + # Look for common quality indicators that appear after titles + quality_match = re.search(r'[\.\s\-_]+(480p|720p|1080p|2160p|4k|HD|FHD|UHD|BluRay|WEB|HDCAM|DVDRip|BDRip|WEBRip)', title, flags=re.IGNORECASE) + if quality_match: + title = title[:quality_match.start()] + + # Convert common separators to spaces + title = re.sub(r'[\.\-_]+', ' ', title) + + # Clean up multiple spaces + title = re.sub(r'\s+', ' ', title) + + # Remove leading/trailing whitespace title = title.strip() - - # Remove common prefixes/suffixes - title = re.sub(r'^(www\.|download|free|movie|film)\s*', '', title, flags=re.IGNORECASE) - title = re.sub(r'\s*(download|free|movie|film)$', '', title, flags=re.IGNORECASE) - + + # Remove common prefixes that might appear at the start + title = re.sub(r'^(www\.|download\s+|free\s+)', '', title, flags=re.IGNORECASE) + + # Handle special cases where title might have colons (like "Avengers: Endgame") + # Keep colons as they're part of legitimate titles + return title -# Test cases based on your examples +# Test cases based on your examples and common patterns test_files = [ + # Your original examples "Avengers: Endgame 1080P Bluray HEVC", "Avengers Endgame 720P WEB HEVC", "Avengers: Infinity 1080P WEB HEVC", "Avengers Triology 480P Bluray HEVC", + + # Movie examples with years + "Sinners.2025.1080p.WEB-DL.x264", + "The.Matrix.1999.720p.BDRip.x264.AAC", + "Spider-Man.No.Way.Home.2021.1080p.WEBRip.x265.HEVC", + "John.Wick.Chapter.4.2023.2160p.4K.WEB-DL.x265", "Avengers.Infinity.War.2018.1080p.BluRay.x264-SPARKS", - "The.Matrix.1999.720p.BDRip.x264.AAC-YTS", - "Spider-Man No Way Home (2021) 1080p WEBRip x265 HEVC 10bit AAC 5.1", - "John.Wick.Chapter.4.2023.2160p.4K.WEB-DL.x265.10bit.HDR.DDP5.1.Atmos", - "Game.of.Thrones.S08E06.The.Iron.Throne.1080p.AMZN.WEB-DL.DDP5.1.H.264" + + # TV Series examples with seasons + "The.Better.Sister.S01E02.1080p.WEB.x264", + "Game.of.Thrones.S08E06.The.Iron.Throne.1080p.AMZN.WEB-DL", + "Breaking.Bad.S05E14.Ozymandias.720p.BluRay.x264", + "Stranger.Things.S04E01.The.Hellfire.Club.2160p.NF.WEB-DL", + "The.Office.S02E01.The.Dundies.720p.WEB-DL", + + # Edge cases + "The.Lord.of.the.Rings.The.Fellowship.of.the.Ring.2001.Extended.1080p", + "Marvel's.Agents.of.S.H.I.E.L.D.S01E01.720p.HDTV", + "Fast.and.Furious.9.The.Fast.Saga.2021.1080p.BluRay" ] print("Title Cleaning Test Results:") From 9ecc52dea8b85ea502c523376a2e8573011d4d53 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 02:41:56 +0100 Subject: [PATCH 06/14] refactor: enhance auto_filter function to accept user_id for improved user context handling --- plugins/pm_filter.py | 12 ++++-- test_title_cleaning.py | 89 ------------------------------------------ 2 files changed, 9 insertions(+), 92 deletions(-) delete mode 100644 test_title_cleaning.py diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 6aca02e..93886aa 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -1061,7 +1061,7 @@ async def cb_handler(client: Client, query: CallbackQuery): -async def auto_filter(client, msg, s, spoll=False, title_search=None): +async def auto_filter(client, msg, s, spoll=False, title_search=None, user_id=None): if not spoll: message = msg settings = await get_settings(message.chat.id) @@ -1080,7 +1080,12 @@ async def auto_filter(client, msg, s, spoll=False, title_search=None): settings = await get_settings(msg.message.chat.id) message = msg.message.reply_to_message # msg will be callback query search, files, offset, total_results = spoll - req = message.from_user.id if message and message.from_user else 0 + + # Use provided user_id if available, otherwise extract from message + if user_id is not None: + req = user_id + else: + req = message.from_user.id if message and message.from_user else 0 key = f"{message.chat.id}-{message.id}" temp.FILES[key] = files BUTTONS[key] = search @@ -1244,7 +1249,8 @@ async def title_callback(client, query): if query.data.startswith("title#"): _, title = query.data.split("#", 1) s = await query.message.edit(f"⚠️ Searching for `{title}`...") - await auto_filter(client, query.message, s, title_search=title) + # Pass the actual user ID who clicked the button, not the message sender + await auto_filter(client, query.message, s, title_search=title, user_id=query.from_user.id) elif query.data.startswith("title_next_"): _, offset = query.data.split("_", 2) diff --git a/test_title_cleaning.py b/test_title_cleaning.py deleted file mode 100644 index 2eca5f0..0000000 --- a/test_title_cleaning.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to demonstrate the enhanced title cleaning functionality -""" - -import re - -def clean_title(filename): - """Clean filename to extract just the movie/show title""" - title = filename - - # Remove file extensions first - title = re.sub(r'\.(mkv|mp4|avi|mov|wmv|flv|webm|m4v|3gp|ts|m2ts)$', '', title, flags=re.IGNORECASE) - - # Strategy: Extract title from beginning until we hit dates or season markers - # Most titles are clean alphanumeric at the start, noise comes after - - # Find where the title likely ends by looking for: - # 1. Year patterns (1900-2099) - # 2. Season/Episode patterns (S01, S1, Season 1, etc.) - # 3. Quality indicators that commonly appear after titles - - # Look for year patterns (movies): "Title.2023" or "Title (2023)" or "Title 2023" - year_match = re.search(r'[\.\s\-_\(]+(19|20)\d{2}[\.\s\-_\)]', title) - if year_match: - title = title[:year_match.start()] - - # Look for season/episode patterns (series): "Title.S01E02" or "Title S1 E1" - season_match = re.search(r'[\.\s\-_]+(S\d{1,2}|Season\s*\d{1,2})', title, flags=re.IGNORECASE) - if season_match: - title = title[:season_match.start()] - - # Look for common quality indicators that appear after titles - quality_match = re.search(r'[\.\s\-_]+(480p|720p|1080p|2160p|4k|HD|FHD|UHD|BluRay|WEB|HDCAM|DVDRip|BDRip|WEBRip)', title, flags=re.IGNORECASE) - if quality_match: - title = title[:quality_match.start()] - - # Convert common separators to spaces - title = re.sub(r'[\.\-_]+', ' ', title) - - # Clean up multiple spaces - title = re.sub(r'\s+', ' ', title) - - # Remove leading/trailing whitespace - title = title.strip() - - # Remove common prefixes that might appear at the start - title = re.sub(r'^(www\.|download\s+|free\s+)', '', title, flags=re.IGNORECASE) - - # Handle special cases where title might have colons (like "Avengers: Endgame") - # Keep colons as they're part of legitimate titles - - return title - -# Test cases based on your examples and common patterns -test_files = [ - # Your original examples - "Avengers: Endgame 1080P Bluray HEVC", - "Avengers Endgame 720P WEB HEVC", - "Avengers: Infinity 1080P WEB HEVC", - "Avengers Triology 480P Bluray HEVC", - - # Movie examples with years - "Sinners.2025.1080p.WEB-DL.x264", - "The.Matrix.1999.720p.BDRip.x264.AAC", - "Spider-Man.No.Way.Home.2021.1080p.WEBRip.x265.HEVC", - "John.Wick.Chapter.4.2023.2160p.4K.WEB-DL.x265", - "Avengers.Infinity.War.2018.1080p.BluRay.x264-SPARKS", - - # TV Series examples with seasons - "The.Better.Sister.S01E02.1080p.WEB.x264", - "Game.of.Thrones.S08E06.The.Iron.Throne.1080p.AMZN.WEB-DL", - "Breaking.Bad.S05E14.Ozymandias.720p.BluRay.x264", - "Stranger.Things.S04E01.The.Hellfire.Club.2160p.NF.WEB-DL", - "The.Office.S02E01.The.Dundies.720p.WEB-DL", - - # Edge cases - "The.Lord.of.the.Rings.The.Fellowship.of.the.Ring.2001.Extended.1080p", - "Marvel's.Agents.of.S.H.I.E.L.D.S01E01.720p.HDTV", - "Fast.and.Furious.9.The.Fast.Saga.2021.1080p.BluRay" -] - -print("Title Cleaning Test Results:") -print("=" * 60) -for filename in test_files: - cleaned = clean_title(filename) - print(f"Original: {filename}") - print(f"Cleaned: {cleaned}") - print("-" * 60) From ef41d762009197aed967147fb49a7bcba604cfe7 Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 02:45:49 +0100 Subject: [PATCH 07/14] refactor: enhance title callback to handle message edit failures gracefully --- plugins/pm_filter.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 93886aa..787a4c7 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -1248,7 +1248,17 @@ async def show_title_selection(client, message, titles, s): async def title_callback(client, query): if query.data.startswith("title#"): _, title = query.data.split("#", 1) - s = await query.message.edit(f"⚠️ Searching for `{title}`...") + try: + s = await query.message.edit(f"🔍 Searching for '{title}'...") + except Exception as e: + # If edit fails, try to send a new message + try: + await query.message.delete() + s = await query.message.reply_text(f"🔍 Searching for '{title}'...") + except: + # If all else fails, just send without deleting + s = await query.message.reply_text(f"🔍 Searching for '{title}'...") + # Pass the actual user ID who clicked the button, not the message sender await auto_filter(client, query.message, s, title_search=title, user_id=query.from_user.id) From 2843af003917fcb1e8f184ddb6d02356e2e18deb Mon Sep 17 00:00:00 2001 From: Olakunle Date: Sat, 31 May 2025 03:02:46 +0100 Subject: [PATCH 08/14] refactor: update title callback to delete old message and send a new one to avoid MESSAGE_NOT_MODIFIED error --- plugins/pm_filter.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 787a4c7..85600dd 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -1248,16 +1248,16 @@ async def show_title_selection(client, message, titles, s): async def title_callback(client, query): if query.data.startswith("title#"): _, title = query.data.split("#", 1) + + # Instead of editing, delete the old message and send a new one + # This completely avoids the MESSAGE_NOT_MODIFIED error try: - s = await query.message.edit(f"🔍 Searching for '{title}'...") - except Exception as e: - # If edit fails, try to send a new message - try: - await query.message.delete() - s = await query.message.reply_text(f"🔍 Searching for '{title}'...") - except: - # If all else fails, just send without deleting - s = await query.message.reply_text(f"🔍 Searching for '{title}'...") + await query.message.delete() + except: + pass # If deletion fails, continue anyway + + # Send a fresh message with the search status + s = await query.message.reply_text(f"🔍 Searching for '{title}'...") # Pass the actual user ID who clicked the button, not the message sender await auto_filter(client, query.message, s, title_search=title, user_id=query.from_user.id) From b26911fe237297b16ab947657185a7591efa3b15 Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 09:13:06 +0300 Subject: [PATCH 09/14] Update commands.py --- plugins/commands.py | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/plugins/commands.py b/plugins/commands.py index 4b903f9..81b45d4 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -29,8 +29,7 @@ async def start(client, message): wish = get_wish() user = message.from_user.mention if message.from_user else "Dear" btn = [[ - InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs ᴄʜᴀɴɴᴇʟ ⚡️', url=UPDATES_LINK), - InlineKeyboardButton('💡 sᴜᴘᴘᴏʀᴛ ɢʀᴏᴜᴘ 💡', url=SUPPORT_LINK) + InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs ᴄʜᴀɴɴᴇʟ ⚡️', url=UPDATES_LINK) ]] await message.reply(text=f"ʜᴇʏ {user}, {wish}\nʜᴏᴡ ᴄᴀɴ ɪ ʜᴇʟᴘ ʏᴏᴜ??", reply_markup=InlineKeyboardMarkup(btn)) return @@ -54,16 +53,10 @@ async def start(client, message): if (len(message.command) != 2) or (len(message.command) == 2 and message.command[1] == 'start'): buttons = [[ - InlineKeyboardButton("+ ᴀᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ɢʀᴏᴜᴘ +", url=f'http://t.me/{temp.U_NAME}?startgroup=start') + InlineKeyboardButton('ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK) ],[ - InlineKeyboardButton('ℹ️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('🧑‍💻 sᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('👨‍🚒 ʜᴇʟᴘ', callback_data='help'), - InlineKeyboardButton('🔎 sᴇᴀʀᴄʜ ɪɴʟɪɴᴇ', switch_inline_query_current_chat=''), - InlineKeyboardButton('📚 ᴀʙᴏᴜᴛ', callback_data='about') - ],[ - InlineKeyboardButton('🤑 Buy Premium', callback_data='premium') + InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), + InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about') ]] reply_markup = InlineKeyboardMarkup(buttons) await message.reply_photo( @@ -161,17 +154,11 @@ async def start(client, message): btn = [[ InlineKeyboardButton("✛ ᴡᴀᴛᴄʜ & ᴅᴏᴡɴʟᴏᴀᴅ ✛", callback_data=f"stream#{file['_id']}") ],[ - InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('💡 ꜱᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('⁉️ ᴄʟᴏsᴇ ⁉️', callback_data='close_data') + InlineKeyboardButton('❌ Delete Files ❌', callback_data='close_data') ]] else: btn = [[ - InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('💡 ꜱᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('⁉️ ᴄʟᴏsᴇ ⁉️', callback_data='close_data') + InlineKeyboardButton('❌ Delete Files ❌', callback_data='close_data') ]] msg = await client.send_cached_media( @@ -220,17 +207,11 @@ async def start(client, message): btn = [[ InlineKeyboardButton("✛ ᴡᴀᴛᴄʜ & ᴅᴏᴡɴʟᴏᴀᴅ ✛", callback_data=f"stream#{file_id}") ],[ - InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('💡 ꜱᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('⁉️ ᴄʟᴏsᴇ ⁉️', callback_data='close_data') + InlineKeyboardButton('❌ Delete Files ❌', callback_data='close_data') ]] else: btn = [[ - InlineKeyboardButton('⚡️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('💡 ꜱᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('⁉️ ᴄʟᴏsᴇ ⁉️', callback_data='close_data') + InlineKeyboardButton('❌ Delete Files ❌', callback_data='close_data') ]] vp = await client.send_cached_media( chat_id=message.from_user.id, From 76254656a1c28bef557bcd1822cec3fd5a758962 Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 09:15:50 +0300 Subject: [PATCH 10/14] Update pm_filter.py --- plugins/pm_filter.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 85600dd..8147e0c 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -581,16 +581,10 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data == "start": buttons = [[ - InlineKeyboardButton("+ ᴀᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ɢʀᴏᴜᴘ +", url=f'http://t.me/{temp.U_NAME}?startgroup=start') + InlineKeyboardButton('ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK) ],[ - InlineKeyboardButton('ℹ️ ᴜᴘᴅᴀᴛᴇs', url=UPDATES_LINK), - InlineKeyboardButton('🧑‍💻 ꜱᴜᴘᴘᴏʀᴛ', url=SUPPORT_LINK) - ],[ - InlineKeyboardButton('👨‍🚒 ʜᴇʟᴘ', callback_data='help'), - InlineKeyboardButton('🔎 ɪɴʟɪɴᴇ', switch_inline_query_current_chat=''), - InlineKeyboardButton('📚 ᴀʙᴏᴜᴛ', callback_data='about') - ],[ - InlineKeyboardButton('🤑 Buy Premium', callback_data='premium') + InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), + InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about') ]] reply_markup = InlineKeyboardMarkup(buttons) await query.edit_message_media( From 7bf671a8934474d8cf45eb176e7562b3ca4e480b Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 09:17:23 +0300 Subject: [PATCH 11/14] Update info.py --- info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.py b/info.py index ebca142..2159945 100644 --- a/info.py +++ b/info.py @@ -108,10 +108,10 @@ def is_valid_ip(ip): USE_CAPTION_FILTER = is_enabled('USE_CAPTION_FILTER', False) IS_VERIFY = is_enabled('IS_VERIFY', False) AUTO_DELETE = is_enabled('AUTO_DELETE', False) -WELCOME = is_enabled('WELCOME', False) +WELCOME = is_enabled('WELCOME', True) PROTECT_CONTENT = is_enabled('PROTECT_CONTENT', False) LONG_IMDB_DESCRIPTION = is_enabled("LONG_IMDB_DESCRIPTION", False) -LINK_MODE = is_enabled("LINK_MODE", True) +LINK_MODE = is_enabled("LINK_MODE", False) AUTO_FILTER = is_enabled('AUTO_FILTER', True) IMDB = is_enabled('IMDB', True) SPELL_CHECK = is_enabled("SPELL_CHECK", True) @@ -158,4 +158,4 @@ def is_valid_ip(ip): if len(UPI_ID) == 0 or len(UPI_NAME) == 0: logger.info('IS_PREMIUM disabled due to empty UPI_ID or UPI_NAME') IS_PREMIUM = False - \ No newline at end of file + From 66f258e1289b0f7df3ae958ce292c452d626c867 Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 09:46:08 +0300 Subject: [PATCH 12/14] Update Script.py --- Script.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Script.py b/Script.py index fb7aaee..1af44fd 100644 --- a/Script.py +++ b/Script.py @@ -2,7 +2,7 @@ class script(object): START_TXT = """ʜᴇʏ {}, {} -ɪ ᴀᴍ ᴘᴏᴡᴇʀғᴜʟ ᴀᴜᴛᴏ ғɪʟᴛᴇʀ ᴡɪᴛʜ ʟɪɴᴋ sʜᴏʀᴛᴇɴᴇʀ ʙᴏᴛ. ʏᴏᴜ ᴄᴀɴ ᴜꜱᴇ ᴀꜱ ᴀᴜᴛᴏ ғɪʟᴛᴇʀ ᴡɪᴛʜ ʟɪɴᴋ sʜᴏʀᴛᴇɴᴇʀ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ... ɪᴛ'ꜱ ᴇᴀꜱʏ ᴛᴏ ᴜꜱᴇ ᴊᴜsᴛ ᴀᴅᴅ ᴍᴇ ᴀꜱ ᴀᴅᴍɪɴ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ ɪ ᴡɪʟʟ ᴘʀᴏᴠɪᴅᴇ ᴛʜᴇʀᴇ ᴍᴏᴠɪᴇꜱ ᴡɪᴛʜ ʏᴏᴜʀ ʟɪɴᴋ ꜱʜᴏʀᴛᴇɴᴇʀ... ♻️""" +ɪ ᴀᴍ ᴘᴏᴡᴇʀғᴜʟ ᴀᴜᴛᴏ ғɪʟᴛᴇʀ ʙᴏᴛ. ʏᴏᴜ ᴄᴀɴ ᴜꜱᴇ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ. ɪᴛ'ꜱ ᴇᴀꜱʏ ᴛᴏ ᴜꜱᴇ ᴊᴜsᴛ ᴀᴅᴅ ᴍᴇ ᴀꜱ ᴀᴅᴍɪɴ ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ ɪ ᴡɪʟʟ ᴘʀᴏᴠɪᴅᴇ ᴛʜᴇʀᴇ ᴍᴏᴠɪᴇꜱ""" MY_ABOUT_TXT = """★ Server: Heroku ★ Database: MongoDB @@ -44,21 +44,11 @@ class script(object): 👉 Please read the Instructions to get better results. 👉 Or not been released yet.""" - IMDB_TEMPLATE = """✅ I Found: {query} - -🏷 Title: {title} + IMDB_TEMPLATE = """🏷 Title: {title} 🎭 Genres: {genres} -📆 Year: {year} -🌟 Rating: {rating} / 10 -☀️ Languages: {languages} -📀 RunTime: {runtime} Minutes - -🗣 Requested by: {message.from_user.mention} -©️ Powered by: {message.chat.title}""" - - FILE_CAPTION = """{file_name} +🌟 Rating: {rating} / 10""" -🚫 ᴘʟᴇᴀsᴇ ᴄʟɪᴄᴋ ᴏɴ ᴛʜᴇ ᴄʟᴏsᴇ ʙᴜᴛᴛᴏɴ ɪꜰ ʏᴏᴜ ʜᴀᴠᴇ sᴇᴇɴ ᴛʜᴇ ᴍᴏᴠɪᴇ 🚫""" + FILE_CAPTION = """{file_name}""" WELCOME_TEXT = """👋 Hello {mention}, Welcome to {title} group! 💞""" From 06a72d71e63aa6a4cf95aa205da965e324592d86 Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 09:57:49 +0300 Subject: [PATCH 13/14] Update pm_filter.py --- plugins/pm_filter.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 8147e0c..76ea350 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -36,8 +36,6 @@ async def pm_search(client, message): files, n_offset, total = await get_search_results(message.text) btn = [[ InlineKeyboardButton("🗂 ᴄʟɪᴄᴋ ʜᴇʀᴇ 🗂", url=FILMS_LINK) - ],[ - InlineKeyboardButton('🤑 Buy Premium', url=f"https://t.me/{temp.U_NAME}?start=premium") ]] await message.reply_text(f"I found {total} results for your query. Buy premium to access them.", reply_markup=InlineKeyboardMarkup(btn)) @@ -164,9 +162,6 @@ async def next_page(bot, query): InlineKeyboardButton("ɴᴇxᴛ »", callback_data=f"next_{req}_{key}_{n_offset}") ] ) - btn.append( - [InlineKeyboardButton('🤑 Buy Premium', url=f"https://t.me/{temp.U_NAME}?start=premium")] - ) await query.message.edit_text(cap + files_link + del_msg, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=True, parse_mode=enums.ParseMode.HTML) @Client.on_callback_query(filters.regex(r"^languages")) @@ -501,7 +496,7 @@ async def cb_handler(client: Client, query: CallbackQuery): InlineKeyboardButton("ᴡᴀᴛᴄʜ ᴏɴʟɪɴᴇ", url=watch), InlineKeyboardButton("ꜰᴀsᴛ ᴅᴏᴡɴʟᴏᴀᴅ", url=download) ],[ - InlineKeyboardButton('❌ ᴄʟᴏsᴇ ❌', callback_data='close_data') + InlineKeyboardButton('❌ Delete Files ❌', callback_data='close_data') ]] reply_markup=InlineKeyboardMarkup(btn) await query.edit_message_reply_markup( @@ -594,10 +589,10 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data == "about": buttons = [[ - InlineKeyboardButton('📊 sᴛᴀᴛᴜs 📊', callback_data='stats'), - InlineKeyboardButton('🤖 sᴏᴜʀᴄᴇ ᴄᴏᴅᴇ 🤖', callback_data='source') + InlineKeyboardButton('sᴛᴀᴛᴜs', callback_data='stats'), + InlineKeyboardButton('sᴏᴜʀᴄᴇ ᴄᴏᴅᴇ', callback_data='source') ],[ - InlineKeyboardButton('🧑‍💻 ʙᴏᴛ ᴏᴡɴᴇʀ 🧑‍💻', callback_data='owner') + InlineKeyboardButton('ʙᴏᴛ ᴏᴡɴᴇʀ', callback_data='owner') ],[ InlineKeyboardButton('« ʙᴀᴄᴋ', callback_data='start') ]] From 9c0bacccbe366912da15251af45c58bdfee81d2f Mon Sep 17 00:00:00 2001 From: Sahed Sarker Date: Thu, 12 Jun 2025 10:08:32 +0300 Subject: [PATCH 14/14] Update pm_filter.py --- plugins/pm_filter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/pm_filter.py b/plugins/pm_filter.py index 76ea350..3cdac2e 100644 --- a/plugins/pm_filter.py +++ b/plugins/pm_filter.py @@ -1119,9 +1119,6 @@ async def auto_filter(client, msg, s, spoll=False, title_search=None, user_id=No btn.insert(0, [InlineKeyboardButton("♻️ sᴇɴᴅ ᴀʟʟ ♻️", callback_data=f"send_all#{key}#{req}")] ) - btn.append( - [InlineKeyboardButton('🤑 Buy Premium', url=f"https://t.me/{temp.U_NAME}?start=premium")] - ) imdb = await get_poster(search, file=(files[0])['file_name']) if settings["imdb"] else None TEMPLATE = settings['template'] if imdb: