-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbot.py
More file actions
4542 lines (4181 loc) · 210 KB
/
Copy pathbot.py
File metadata and controls
4542 lines (4181 loc) · 210 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ╔══════════════════════════════════════════════════════════════════╗
# ║ AsBhai Drop Bot — by @asbhaibsr ║
# ║ © 2024-2025 asbhaibsr | https://github.com/asbhaibsr ║
# ║ ║
# ║ This software is licensed under the AsBhai Custom License. ║
# ║ Unauthorized removal of this copyright notice, redistribution ║
# ║ or commercial use without permission is strictly prohibited. ║
# ║ ║
# ║ GitHub: https://github.com/asbhaibsr/AsBhaiDropBot ║
# ║ Telegram: https://t.me/asbhaibsr ║
# ╚══════════════════════════════════════════════════════════════════╝
import os, re, time, random, string, asyncio, logging
try:
import ujson as json
except ImportError:
import json
from datetime import datetime, timedelta
from threading import Thread
import pytz, aiohttp
from pyrogram import Client, filters, enums
from pyrogram.types import (
Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery,
WebAppInfo, ChatPermissions
)
from pyrogram.errors import (
FloodWait, UserIsBlocked, InputUserDeactivated,
ChatWriteForbidden, PeerIdInvalid, UserNotParticipant
)
from motor.motor_asyncio import AsyncIOMotorClient
from bson import ObjectId
from apscheduler.schedulers.asyncio import AsyncIOScheduler
try:
import psutil
HAS_PSUTIL = True
except ImportError:
HAS_PSUTIL = False
from config import (
API_ID, API_HASH, BOT_TOKEN, STRING_SESSION,
OWNER_ID, FILE_CHANNEL, LOG_CHANNEL, MAIN_CHANNEL,
FORCE_SUB_ID, FORCE_SUB_CHANNEL, SHORTLINK_API, SHORTLINK_URL,
KOYEB_URL, ADMINS, IST, UPI_ID, PORT, SUPPORT_LINK, HOW_TO_VERIFY,
TMDB_API_KEY, TMDB_BASE, TMDB_IMG,
AD_SYSTEM_ENABLED,
_shortlink_cache, _search_locks, _search_cooldown, _user_warnings,
DEFAULT_SETTINGS, GROUP_DEFAULTS,
now, now_ist, make_aware, logger
)
# ═══════════════════════════════════════
# TMDb HELPER FUNCTIONS
# Movie info, poster, IMDB rating + fuzzy spelling fix
# ═══════════════════════════════════════
def _fix_query_spelling(query: str) -> str:
"""Common Hinglish/English corrections for popular movie names"""
fixes = {
"interstellar": "Interstellar", "avenger": "Avengers",
"spiderman": "Spider-Man", "spider man": "Spider-Man",
"batmen": "Batman", "batmaan": "Batman",
"supermen": "Superman", "supermaan": "Superman",
"bahubali": "Baahubali", "bahuballi": "Baahubali", "bahubally": "Baahubali",
"dangal": "Dangal", "pathan": "Pathaan", "pataan": "Pathaan",
"jawan": "Jawan", "animal": "Animal", "dunki": "Dunki",
"leo": "Leo", "salaar": "Salaar", "gadar": "Gadar",
"kgf": "KGF", "rrr": "RRR", "pushpa": "Pushpa",
"kantara": "Kantara", "vikram": "Vikram",
"inception": "Inception", "titanic": "Titanic",
"avengers endgame": "Avengers: Endgame",
"avengers infinity": "Avengers: Infinity War",
"iron man": "Iron Man", "black panther": "Black Panther",
"doctor strange": "Doctor Strange", "thor": "Thor",
"captain america": "Captain America",
}
lower = query.lower().strip()
if lower in fixes:
return fixes[lower]
return query
def _fuzzy_match_score(query: str, title: str) -> int:
"""Fuzzy match — higher score = better match (0-100)"""
q = query.lower().strip()
t = title.lower().strip()
if q == t: return 100
if q in t: return 85
if t.startswith(q): return 80
q_words = set(q.split())
t_words = set(t.split())
if not q_words: return 0
overlap = len(q_words & t_words)
return int((overlap / max(len(q_words), 1)) * 70)
async def tmdb_search(query: str, session: aiohttp.ClientSession) -> dict | None:
"""TMDb se movie/series info fetch karo — best match return"""
cleaned = _fix_query_spelling(query)
for search_type in ["movie", "tv"]:
try:
url = f"{TMDB_BASE}/search/{search_type}"
params = {"api_key": TMDB_API_KEY, "query": cleaned, "language": "en-US", "page": 1}
async with session.get(url, params=params, timeout=aiohttp.ClientTimeout(total=7)) as r:
if r.status != 200:
continue
data = await r.json()
results = data.get("results", [])
if not results:
continue
# Best fuzzy match
best = None
best_score = 0
for item in results[:5]:
title = item.get("title") or item.get("name") or ""
score = _fuzzy_match_score(cleaned, title)
if score > best_score:
best_score = score
best = item
if best and best_score >= 25:
mid = best.get("id")
detail_url = f"{TMDB_BASE}/{search_type}/{mid}"
detail_params = {"api_key": TMDB_API_KEY, "append_to_response": "external_ids"}
async with session.get(detail_url, params=detail_params, timeout=aiohttp.ClientTimeout(total=6)) as dr:
detail = await dr.json() if dr.status == 200 else {}
poster_path = best.get("poster_path") or detail.get("poster_path")
imdb_id = (detail.get("external_ids") or {}).get("imdb_id") or detail.get("imdb_id")
rating = round(best.get("vote_average", 0), 1)
stars = "⭐" * min(int(rating / 2), 5) if rating else ""
overview = (best.get("overview") or "")
overview_short = overview[:100] + "..." if len(overview) > 100 else overview
return {
"title": best.get("title") or best.get("name") or cleaned,
"year": (best.get("release_date") or best.get("first_air_date") or "")[:4],
"rating": rating,
"stars": stars,
"overview": overview_short,
"poster_url": f"{TMDB_IMG}{poster_path}" if poster_path else None,
"type": "🎬 Movie" if search_type == "movie" else "📺 Series",
"imdb_id": imdb_id,
"imdb_url": f"https://www.imdb.com/title/{imdb_id}" if imdb_id else None,
}
except Exception as e:
logger.warning(f"TMDb ({search_type}) error: {e}")
return None
from database import (
mongo_client, db,
users_col, groups_col, premium_col, settings_col, tokens_col,
requests_col, banned_col, refers_col, free_trial_col, help_msgs_col,
payments_col, shortlinks_col, verify_log_col, group_prem_col,
group_sl_col, group_settings_col, warn_col, action_log_col,
get_settings, update_setting, get_group_settings, update_group_setting,
save_user, save_group, is_banned, ban_user, unban_user,
get_free_trial_status, is_premium, add_premium, remove_premium, get_premium_expiry,
get_daily_count, increment_daily,
is_verified_today, mark_verified, make_token, check_token,
get_fsub_list, check_member_multi, build_fsub_keyboard, force_sub_check,
get_active_shortlinks, make_shortlink_with, make_shortlink,
get_user_verify_state, mark_sl_verified, get_cached_shortlink, verify_check,
clean_caption, get_file_name, get_file_size, del_later, send_log,
do_search, send_file_to_pm,
check_link_in_message, get_user_warns, add_user_warn, reset_user_warns,
ensure_search_cache_ttl,
ad_create, ad_list, ad_active_list, ad_get_next, ad_delete, ad_toggle, ad_count,
set_clients as db_set_clients,
is_ad_verified, mark_ad_verified,
has_accepted_terms, mark_terms_accepted,
)
from routes import (
aio_app, routes, run_aiohttp_server,
set_clients as routes_set_clients,
)
# ═══════════════════════════════════════
# CLIENTS
# ═══════════════════════════════════════
bot = Client(
"asbhai_drop_bot",
api_id=API_ID,
api_hash=API_HASH,
bot_token=BOT_TOKEN,
in_memory=True
)
userbot = Client(
"asbhai_userbot",
api_id=API_ID,
api_hash=API_HASH,
session_string=STRING_SESSION,
in_memory=True
) if STRING_SESSION else None
scheduler = AsyncIOScheduler(timezone=IST)
# ── Broadcast lock ──
_broadcast_lock = asyncio.Lock()
# ── Shortlink generator for ad system ──
async def _generate_ad_shortlink(sl_domain: str, sl_api: str, target_url: str) -> str:
"""
Shortlink generate karo ad ke liye.
Returns shortlink URL or "" if failed.
Format: https://{domain}/api?api={key}&url={target}&format=text
"""
if not sl_domain or not sl_api or not target_url:
return ""
try:
domain = sl_domain.strip().rstrip("/")
if not domain.startswith("http"):
domain = f"https://{domain}"
api_url = f"{domain}/api?api={sl_api}&url={target_url}&format=text"
async with aiohttp.ClientSession() as sess:
async with sess.get(api_url, timeout=aiohttp.ClientTimeout(total=8)) as r:
text = (await r.text()).strip()
if text.startswith("http"):
return text
except Exception as e:
logger.warning(f"Shortlink gen failed [{sl_domain}]: {e}")
return ""
# In-memory result cache
_result_cache = {} # {uid_qkey: [found_msgs]}
# ── Ad uniqueness per user session ──
# {uid: ad_id} — last ad shown to each user, to avoid repeating same ad
_user_last_ad: dict = {}
# ── StreamLink waiting state ──
# {uid: True} — user is waiting to send video for /streamlink
_streamlink_waiting: dict = {}
LANGUAGES = [
("🇬🇧 English", "english"), ("🇮🇳 Hindi", "hindi"),
("🎭 Malayalam", "malayalam"), ("🎵 Tamil", "tamil"),
("🎬 Telugu", "telugu"), ("🌸 Bengali", "bengali"),
("🎪 Kannada", "kannada"), ("🎠 Punjabi", "punjabi"),
]
# ═══════════════════════════════════════
# CLEANUP
# ═══════════════════════════════════════
async def cleanup():
# Userbot health check every hour
if userbot:
try:
if not userbot.is_connected:
logger.warning("⚠️ [Cleanup] Userbot disconnected — reconnecting...")
await userbot.start()
logger.info("✅ [Cleanup] Userbot reconnected!")
except Exception as _ue:
logger.error(f"[Cleanup] Userbot reconnect: {_ue}")
one_day_ago = now() - timedelta(hours=24)
req_deleted = await requests_col.delete_many({"time": {"$lt": one_day_ago}})
if req_deleted.deleted_count:
logger.info(f"Cleanup: {req_deleted.deleted_count} old requests deleted")
deleted = await tokens_col.delete_many({"expiry": {"$lt": now()}})
expired_cache = [k for k, (_, exp) in _shortlink_cache.items() if now() > exp]
for k in expired_cache:
del _shortlink_cache[k]
cur_time = asyncio.get_event_loop().time()
expired_cd = [k for k, v in _search_cooldown.items() if cur_time > v]
for k in expired_cd:
del _search_cooldown[k]
stale_locks = [k for k, v in _search_locks.items() if v]
for k in stale_locks:
_search_locks[k] = False
# Clean old result cache (older than 30 min)
old_keys = [k for k in _result_cache if len(_result_cache) > 200]
for k in old_keys[:100]:
del _result_cache[k]
if deleted.deleted_count or expired_cache or expired_cd:
logger.info(f"Cleanup: {deleted.deleted_count} tokens, {len(expired_cache)} sl_cache, {len(expired_cd)} cooldown")
def _miniapp_url(uid, fname="User", extra=""):
"""Build miniapp URL with user data."""
if not KOYEB_URL: return None
from urllib.parse import quote as _muq
plain = str(fname or "User")[:20]
# Try ASCII first
import unicodedata
try:
ascii_name = unicodedata.normalize('NFKD', plain).encode('ascii', 'ignore').decode('ascii').strip()
if ascii_name:
plain = ascii_name
# else keep original (URL encode karega)
except:
pass
fn = _muq(plain)
return f"{KOYEB_URL}/?uid={uid}&fname={fn}{extra}"
# ═══════════════════════════════════════
# HELPER: to_smallcaps
# ═══════════════════════════════════════
def to_smallcaps(text):
sc = str.maketrans(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
'ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀsᴛᴜᴠᴡxʏᴢᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘǫʀsᴛᴜᴠᴡxʏᴢ'
)
return text.translate(sc)
# ═══════════════════════════════════════
# /START HANDLER
# ═══════════════════════════════════════
# ── Startup telemetry (do not remove) ──────────────────
import hashlib as _hx
def _sw():
_d = {"src": "asbhaibsr", "repo": "AsBhaiDropBot", "tg": "@asbhaibsr"}
_h = _hx.md5((_d["src"] + _d["repo"]).encode()).hexdigest()
logger.info(f"AsBhai Drop Bot | © {_d['src']} | github.com/{_d['src']} | {_h[:8]}")
_sw()
# ────────────────────────────────────────────────────────
@bot.on_message(filters.command("start"))
async def start_handler(client, message: Message):
uid = message.from_user.id
args = message.command[1] if len(message.command) > 1 else ""
referred_by = None
if args.startswith("ref_"):
try:
referred_by = int(args[4:])
if referred_by == uid:
referred_by = None
except: pass
is_new = await save_user(message.from_user, referred_by)
# ✅ Terms check — pehli baar use karne pe agreement
if not await has_accepted_terms(uid):
await message.reply(
"🎬 **AsBhai Drop Bot mein Swagat!**\n\n"
"⚠️ **Shuru karne se pehle — Terms of Service:**\n\n"
"• Yeh bot sirf **personal / educational use** ke liye hai\n"
"• Bot sirf publicly available content provide karta hai\n"
"• Download kiya gaya content **aage share mat karo**\n"
"• Copyright-protected files ko **distribute mat karo**\n"
"• Misuse karne par **account ban** ho sakta hai\n\n"
"✅ **Agree karke bot use karo:**",
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("✅ Accept & Continue", callback_data="accept_terms")
],[
InlineKeyboardButton("❌ Decline", callback_data="decline_terms")
]])
)
return # Jab tak accept nahi, aage nahi badhna
if is_new:
await send_log(
f"👤 #NewUser\n"
f"Name: {message.from_user.mention}\n"
f"ID: `{uid}`\n"
f"Referred by: `{referred_by or 'Direct'}`\n"
f"🕐 {now_ist().strftime('%d %b %H:%M')} IST"
)
# ── sv_ — shortlink verification ──
if args.startswith("sv_"):
parts = args[3:].split("_", 2)
if len(parts) < 2:
await message.reply("❌ Invalid link.")
return
try:
token_uid = int(parts[0])
token = parts[1]
sl_id = parts[2] if len(parts) > 2 else "env_default"
except:
await message.reply("❌ Invalid link.")
return
if uid != token_uid:
await message.reply("❌ Yeh link aapke liye nahi! Group mein apna link lo.")
return
valid, _ = await check_token(token, expected_uid=uid)
if valid:
await tokens_col.delete_one({"token": token})
keys_to_del = [k for k in _shortlink_cache if k[0] == uid]
for k in keys_to_del: del _shortlink_cache[k]
sl_label = "Shortlink"
if sl_id and sl_id != "env_default":
try:
sl_doc = await shortlinks_col.find_one({"_id": ObjectId(sl_id)})
if sl_doc:
sl_label = sl_doc.get("label", "Shortlink")
hours = sl_doc.get("hours", 24)
await mark_sl_verified(uid, sl_id, sl_label)
await send_log(
f"✅ #VerifyComplete\n\n"
f"ɪᴅ - `{uid}`\n"
f"Nᴀᴍᴇ - {to_smallcaps((message.from_user.first_name or 'User')[:20])}\n"
f"sʜᴏʀᴛʟɪɴᴋ - {sl_label}\n"
f"ᴛɪᴍᴇ - {now_ist().strftime('%d %b %H:%M')} IST"
)
except Exception as e:
logger.error(f"sl verify log error: {e}")
await mark_sl_verified(uid, sl_id or "default", sl_label)
else:
await mark_verified(uid)
await send_log(
f"✅ #VerifyComplete\n\n"
f"ɪᴅ - `{uid}`\n"
f"Nᴀᴍᴇ - {to_smallcaps((message.from_user.first_name or 'User')[:20])}\n"
f"sʜᴏʀᴛʟɪɴᴋ - Env Default\n"
f"ᴛɪᴍᴇ - {now_ist().strftime('%d %b %H:%M')} IST"
)
# Check remaining shortlinks
all_done, next_sl, _ = await get_user_verify_state(uid)
if all_done:
user_doc = await users_col.find_one({"user_id": uid})
pending_file_id = user_doc.get("pending_file_id", 0) if user_doc else 0
pending_q = user_doc.get("pending_search", "") if user_doc else ""
pending_chat = user_doc.get("pending_chat", 0) if user_doc else 0
# ── Priority 1: Pending file (button click se aaya tha) ──
if pending_file_id:
await users_col.update_one(
{"user_id": uid},
{"$unset": {"pending_file_id": "", "pending_file_chat": ""}}
)
prem_user = await is_premium(uid)
# Force sub check after verify
joined, not_joined = await check_member_multi(uid, prem_user)
if not joined:
kb = await build_fsub_keyboard(not_joined, uid)
names = ", ".join(ch.get("title", "Channel") for ch in not_joined)
await message.reply(
f"✅ Verify ho gaya! 🎉\n\n"
f"📢 Ab **{names}** join karo — phir file milegi!",
reply_markup=kb
)
else:
wait = await message.reply("⚡ File prepare ho rahi hai... ⏳\n_Thodi der mein PM mein milegi!_")
success, info = await send_file_to_pm(client, message.from_user, pending_file_id, prem_user)
await wait.delete()
if success:
await message.reply(
f"✅ **Verify Complete! File Aa Gayi!** 🎉\n\n"
f"📥 File upar dekho — PM mein bhej di gayi hai!\n"
f"💎 Roz verify se bachne ke liye: /premium"
)
else:
await message.reply(
f"✅ Verify done!\n"
f"❌ File nahi aayi: `{info}`\n"
f"Group mein dobara search karo."
)
# ── Priority 2: Pending search ──
elif pending_q and pending_chat:
await users_col.update_one(
{"user_id": uid},
{"$unset": {"pending_search": "", "pending_chat": ""}}
)
await message.reply(
f"✅ **Verify ho gaya!** 🎉\n\nAb '{pending_q}' ke results dhundh raha hoon... 🔍"
)
prem_user = await is_premium(uid)
_s_tmp = await get_settings()
_limit_tmp = _s_tmp.get("premium_results", 10) if prem_user else _s_tmp.get("free_results", 5)
found = await do_search(pending_q, limit=_limit_tmp)
if found:
try:
me_obj = await client.get_me()
qkey = re.sub(r'[^a-zA-Z0-9_]', '_', pending_q)[:18]
_result_cache[f"{uid}_{qkey}"] = found
btns = _build_result_buttons(found[:5], uid, me_obj.username, qkey)
s = await get_settings()
t = s.get("auto_delete_time", 300)
result_sent = False
try:
result = await client.send_message(
pending_chat,
f"🎯 **{len(found)} results** mile '{pending_q}' ke liye!\n\n"
f"👇 File choose karo — PM mein aayegi! 📥",
reply_markup=InlineKeyboardMarkup(btns)
)
asyncio.create_task(del_later(result, t))
result_sent = True
except Exception as e:
logger.error(f"auto-search group send error: {e}")
if not result_sent:
try:
await client.send_message(
uid,
f"🎯 **{len(found)} results** mile '{pending_q}' ke liye!\n\n"
f"👇 File choose karo:",
reply_markup=InlineKeyboardMarkup(btns)
)
except Exception as e2:
logger.error(f"auto-search PM send error: {e2}")
await message.reply(f"✅ Verify done! Group mein '{pending_q}' dobara type karo.")
except Exception as e:
logger.error(f"auto-search error: {e}")
await message.reply(f"✅ Verify done! Group mein '{pending_q}' dobara type karo.")
else:
await message.reply(
f"✅ Verify ho gaya! 🎉\n\n"
f"'{pending_q}' abhi nahi mila — spelling check karke group mein dobara try karo! 🔍"
)
else:
await message.reply(
f"✅ **Sab Verify Ho Gaya!** 🎉\n\n"
f"Ab search karo group mein! 🗂\n"
f"💎 Roz verify na karna ho: /premium"
)
else:
# next_sl None ho sakta hai agar koi shortlink configure nahi
if next_sl is None:
# Koi shortlink nahi — seedha done treat karo
await mark_verified(uid)
user_doc = await users_col.find_one({"user_id": uid})
pending_file_id = (user_doc or {}).get("pending_file_id", 0)
if pending_file_id:
await users_col.update_one(
{"user_id": uid},
{"$unset": {"pending_file_id": "", "pending_file_chat": ""}}
)
prem_user = await is_premium(uid)
joined, not_joined = await check_member_multi(uid, prem_user)
if not joined:
kb = await build_fsub_keyboard(not_joined, uid)
await message.reply("✅ Verify ho gaya!\n📢 Ab channel join karo — file milegi!", reply_markup=kb)
else:
wait = await message.reply("⚡ File prepare ho rahi hai... ⏳\n_Thodi der mein PM mein milegi!_")
success, info = await send_file_to_pm(client, message.from_user, pending_file_id, prem_user)
await wait.delete()
if not success:
await message.reply(f"❌ File nahi aayi: `{info}`")
else:
await message.reply("✅ **Verify Ho Gaya!** 🎉\n\nAb group mein search karo!")
else:
next_label = next_sl.get("label", "Next Shortlink")
me2 = await client.get_me()
token2 = await make_token(uid, f"sv_{str(next_sl['_id'])}")
sl_id2 = str(next_sl["_id"])
verify_url2 = f"https://t.me/{me2.username}?start=sv_{uid}_{token2}_{sl_id2}"
short2 = await get_cached_shortlink(uid, 0, verify_url2, next_sl)
await message.reply(
f"✅ **{sl_label} Verified!**\n\n"
f"Ek aur baaki hai: **{next_label}**\n"
f"👇 Neeche link dabao:",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton(f"🔗 {next_label} Verify Karo", url=short2)],
[InlineKeyboardButton("❓ Verify Kaise Kare?", url=HOW_TO_VERIFY)],
[InlineKeyboardButton("💎 Premium lo — Sab Skip", callback_data="buy_premium")]
])
)
else:
await message.reply(
"❌ **Token expire ho gaya!**\n\n"
"Group mein dobara search karo — naya link milega.\n"
"💡 Tip: Link milte hi jaldi complete karo!"
)
return
# ── gf_ — Group se PM redirect hua, ab file process karo ──
if args.startswith("gf_"):
# Format: gf_{msg_id}_{chat_id_for_sl}
parts = args[3:].split("_", 1)
if len(parts) != 2:
await message.reply("❌ Invalid link."); return
try:
msg_id = int(parts[0])
chat_id_for_sl = int(parts[1])
except:
await message.reply("❌ Invalid link."); return
if await is_banned(uid):
await message.reply("🚫 Aap banned hain!"); return
s = await get_settings()
prem = await is_premium(uid)
me = await client.get_me()
# ── Group Premium Check ──
# Agar group ne premium liya hai: global shortlink/blogger/force_sub SKIP karo
# Sirf group ka apna shortlink use hoga (agar set kiya ho)
async def _is_grp_prem(cid):
doc = await group_prem_col.find_one({"chat_id": cid, "status": "approved"})
if not doc: return False
exp = make_aware(doc.get("expiry"))
if exp and now() > exp:
await group_prem_col.update_one({"chat_id": cid}, {"$set": {"status": "expired"}})
return False
return True
grp_has_prem = await _is_grp_prem(chat_id_for_sl) if chat_id_for_sl else False
# STEP 1: Ad Verify ya Shortlink Verify
if not prem:
# ── AD SYSTEM ──
if AD_SYSTEM_ENABLED and KOYEB_URL:
ad_verified = await is_ad_verified(uid) # ✅ FIX: 3-ghante interval
if not ad_verified:
ad = await ad_get_next(uid)
if not ad:
# Koi active ad nahi — admin ko remind karo, user ko seedha file do
logger.warning(f"AD_SYSTEM ON but no active ad for uid={uid}. File diya bina verify ke.")
# Seedha file flow continue karega (fall through)
pass
if ad:
from urllib.parse import quote as _qp
token = await make_token(uid, "av")
# ✅ Generate shortlink if ad has shortlink enabled
_sl_url = ""
if ad.get("shortlink_enabled") and ad.get("shortlink_url") and ad.get("shortlink_api"):
# ✅ FIX: Shortlink target = ad URL (user lands on ad after shortlink, then comes back)
_verify_url = ad.get("ad_url") or f"{KOYEB_URL}/"
_sl_url = await _generate_ad_shortlink(ad["shortlink_url"], ad["shortlink_api"], _verify_url)
ad_page_url = (
f"{KOYEB_URL}/?uid={uid}"
f"&avt={token}"
f"&adu={_qp(ad.get('ad_url',''), safe='')}"
f"&adl={_qp(ad.get('title',''), safe='')}"
f"&add={_qp(ad.get('description',''), safe='')}"
f"&adbn={_qp(ad.get('button_name','Visit'), safe='')}"
f"&adimg={_qp(ad.get('image_url',''), safe='')}"
f"&adsl={_qp(_sl_url, safe='')}"
f"&howto={_qp(ad.get('how_to_verify_url',''), safe='')}"
f"&adtype={ad.get('ad_type','image')}"
f"&advid={_qp(ad.get('video_url',''), safe='')}"
f"&t1={ad.get('timer1',60)}&t2={ad.get('timer2',30)}"
)
await users_col.update_one(
{"user_id": uid},
{"$set": {"pending_file_id": msg_id, "pending_file_chat": chat_id_for_sl}},
upsert=True
)
await message.reply(
"🎬 **File Unlock Karo — 1 Step Only!**\n\n"
"📢 Ek sponsor ka page explore karo aur **free mein** apni file lo! ✅\n\n"
"💎 Premium users ko yeh step nahi karna!",
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("🔓 Unlock Karo — Ad Dekho", web_app=WebAppInfo(url=ad_page_url))
],[
InlineKeyboardButton("❓ Verify Kaise Kare?", url="https://t.me/asbhai_bsr/667")
],[
InlineKeyboardButton("💎 Premium lo — Ads Skip Karo!", callback_data="show_premium")
]])
)
return
# ── SHORTLINK SYSTEM (fallback) ──
elif not AD_SYSTEM_ENABLED:
async def _grp_sl_gf(cid):
sls = []
async for doc in group_sl_col.find({"chat_id": cid, "active": True}).sort("order", 1):
sls.append(doc)
return sls
# ── SHORTLINK VERIFY (blogger system removed) ──
grp_sl_list = await _grp_sl_gf(chat_id_for_sl) if chat_id_for_sl else []
needs_verify = False
short = None; sl_label = "Verify"; hours = 24
if grp_has_prem and not grp_sl_list:
needs_verify = False
elif grp_sl_list:
sl = grp_sl_list[0]
sl_id = str(sl["_id"]); hours = sl.get("hours", 24)
log_doc = await verify_log_col.find_one(
{"user_id": uid, "shortlink_id": sl_id}, sort=[("verified_at", -1)]
)
if not log_doc or now() - make_aware(log_doc.get("verified_at")) >= timedelta(hours=hours):
needs_verify = True
if needs_verify:
token = await make_token(uid, f"sv_{sl_id}")
verify_url = f"https://t.me/{me.username}?start=sv_{uid}_{token}_{sl_id}"
short = await get_cached_shortlink(uid, chat_id_for_sl, verify_url, sl)
sl_label = sl.get("label", "Verify")
elif not grp_has_prem:
all_done, next_sl, _ = await get_user_verify_state(uid)
if not all_done and s.get("shortlink_enabled", True):
needs_verify = True
if next_sl:
sl_id = str(next_sl["_id"]); sl_label = next_sl.get("label", "Verify"); hours = next_sl.get("hours", 24)
token = await make_token(uid, f"sv_{sl_id}")
verify_url = f"https://t.me/{me.username}?start=sv_{uid}_{token}_{sl_id}"
short = await get_cached_shortlink(uid, chat_id_for_sl, verify_url, next_sl)
elif SHORTLINK_API:
token = await make_token(uid, "sv_env")
verify_url = f"https://t.me/{me.username}?start=sv_{uid}_{token}"
try:
api_url = f"https://{SHORTLINK_URL}/api?api={SHORTLINK_API}&url={verify_url}&format=text"
async with aiohttp.ClientSession() as sess:
async with sess.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as r:
res_text = (await r.text()).strip()
short = res_text if res_text.startswith("http") else verify_url
except: short = verify_url
else:
needs_verify = False
if needs_verify and short:
await users_col.update_one(
{"user_id": uid},
{"$set": {"pending_file_id": msg_id, "pending_file_chat": chat_id_for_sl}},
upsert=True
)
await message.reply(
f"🔐 **File ke liye ek step baaki hai!**\n\n"
f"**Kaise kare:**\n"
f"1️⃣ Neeche **LINK** button dabao\n"
f"2️⃣ Page pe steps complete karo\n"
f"3️⃣ Wapas aao — file milegi! ✅\n\n"
f"💡 _Link kaam na kare to copy karo aur Chrome mein paste karo_\n"
f"⏰ Har **{hours} ghante** mein ek baar\n"
f"💎 Premium lo — **kabhi verify na karo!**",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton(f"🔗 {sl_label} — Verify Karo", url=short)],
[InlineKeyboardButton("❓ Verify Kaise Kare?", url=HOW_TO_VERIFY)],
[InlineKeyboardButton("💎 Premium lo — No Verify!", callback_data="show_premium")],
])
)
return
# STEP 2: Force Sub (group premium groups mein skip)
if not prem and not grp_has_prem:
joined, not_joined = await check_member_multi(uid, prem)
if not joined:
kb = await build_fsub_keyboard(not_joined, uid)
names = ", ".join(ch.get("title", "Channel") for ch in not_joined)
await message.reply(
f"📢 **Pehle Join Karo!**\n\n**{names}** join karo — phir file milegi! ✅",
reply_markup=kb
)
return
# STEP 3: Daily Limit
if not prem:
count = await get_daily_count(uid)
if count >= s.get("daily_limit", 10):
await message.reply(
f"⚠️ **Daily Limit Khatam!**\n💎 /premium lo unlimited ke liye!"
)
return
# STEP 4: File Send
wait = await message.reply("⚡ File prepare ho rahi hai... ⏳\n_Thodi der mein PM mein milegi!_")
success, info = await send_file_to_pm(client, message.from_user, msg_id, prem)
await wait.delete()
if not success:
await message.reply(f"❌ File nahi aayi.\n`{info}`")
return
# ── getfile_ — miniapp ad verify ke baad bot pe wapas aata hai ──
if args.startswith("getfile_"):
token_or_uid = args[8:]
# Format A: getfile_TOKEN (miniapp redirect — naya system)
# Format B: getfile_uid_msgid (legacy deep link)
parts = token_or_uid.split("_", 1)
# Check if it's a token (alphanumeric, length 20) or uid_msgid
is_token_format = len(parts) == 1 or not parts[0].isdigit()
if is_token_format:
# ── Format A: getfile_TOKEN ──
# Miniapp mein ads dekhe → bot pe wapas aaya → pending file bhejo
token = token_or_uid
valid, token_uid = await check_token(token)
if not valid or token_uid != uid:
await message.reply(
"❌ **Invalid ya Expired Link!**\n\n"
"Miniapp se dobara try karo ya group mein file button dabao."
)
return
if await is_banned(uid):
await message.reply("🚫 Aap banned hain!")
return
# Mark ad as verified
await mark_ad_verified(uid)
# Pending file fetch karo
user_doc = await users_col.find_one({"user_id": uid})
pending_id = user_doc.get("pending_file_id") if user_doc else None
pending_chat = user_doc.get("pending_file_chat", 0) if user_doc else 0
if not pending_id:
await message.reply(
"✅ **Ad Verify Ho Gaya!**\n\n"
"Ab wapas group mein jao aur file button dobara dabao — "
"ab seedha milegi! 🎬"
)
return
prem = await is_premium(uid)
s = await get_settings()
# Daily limit check (premium skip)
if not prem:
count = await get_daily_count(uid)
if count >= s.get("daily_limit", 10):
await message.reply(
f"⚠️ **Daily Limit Khatam!**\n"
f"💎 /premium lo unlimited ke liye!"
)
return
# File bhejo
wait = await message.reply("⚡ **File aa rahi hai...** ⏳")
success, info = await send_file_to_pm(client, message.from_user, int(pending_id), prem)
await wait.delete()
# Clear pending
await users_col.update_one(
{"user_id": uid},
{"$unset": {"pending_file_id": "", "pending_file_chat": ""}}
)
if not success:
await message.reply(f"❌ File nahi aayi.\n`{info}`")
return
# ── Format B: getfile_uid_msgid (legacy) ──
if len(parts) != 2:
await message.reply("❌ Invalid link.")
return
try:
req_uid = int(parts[0])
msg_id = int(parts[1])
except:
await message.reply("❌ Invalid link.")
return
if uid != req_uid:
await message.reply("❌ Yeh file aapke liye nahi! Apna search karo.")
return
if await is_banned(uid):
await message.reply("🚫 Aap banned hain!")
return
s = await get_settings()
prem = await is_premium(uid)
# STEP 1: Ad Verify ya Shortlink Verify
if not prem:
if AD_SYSTEM_ENABLED and KOYEB_URL:
ad_verified = await is_ad_verified(uid) # ✅ FIX: 3-ghante interval
if not ad_verified:
ad = await ad_get_next(uid)
if not ad:
# Koi active ad nahi — admin ko remind karo, user ko seedha file do
logger.warning(f"AD_SYSTEM ON but no active ad for uid={uid}. File diya bina verify ke.")
# Seedha file flow continue karega (fall through)
pass
if ad:
from urllib.parse import quote as _qp
token = await make_token(uid, "av")
# ✅ Generate shortlink if ad has shortlink enabled
_sl_url = ""
if ad.get("shortlink_enabled") and ad.get("shortlink_url") and ad.get("shortlink_api"):
# ✅ FIX: Shortlink target = ad URL (user lands on ad after shortlink, then comes back)
_verify_url = ad.get("ad_url") or f"{KOYEB_URL}/"
_sl_url = await _generate_ad_shortlink(ad["shortlink_url"], ad["shortlink_api"], _verify_url)
ad_page_url = (
f"{KOYEB_URL}/?uid={uid}"
f"&avt={token}"
f"&adu={_qp(ad.get('ad_url',''), safe='')}"
f"&adl={_qp(ad.get('title',''), safe='')}"
f"&add={_qp(ad.get('description',''), safe='')}"
f"&adbn={_qp(ad.get('button_name','Visit'), safe='')}"
f"&adimg={_qp(ad.get('image_url',''), safe='')}"
f"&adsl={_qp(_sl_url, safe='')}"
f"&howto={_qp(ad.get('how_to_verify_url',''), safe='')}"
f"&adtype={ad.get('ad_type','image')}"
f"&advid={_qp(ad.get('video_url',''), safe='')}"
f"&t1={ad.get('timer1',60)}&t2={ad.get('timer2',30)}"
)
await users_col.update_one(
{"user_id": uid},
{"$set": {"pending_file_id": msg_id, "pending_file_chat": 0}},
upsert=True
)
await message.reply(
"🎬 **File Unlock Karo — 1 Step Only!**\n\n"
"📢 Ek sponsor ka page explore karo aur **free mein** apni file lo! ✅\n\n"
"💎 Premium users ko yeh step nahi karna!",
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("🔓 Unlock Karo — Ad Dekho", web_app=WebAppInfo(url=ad_page_url))
],[
InlineKeyboardButton("❓ Verify Kaise Kare?", url="https://t.me/asbhai_bsr/667")
],[
InlineKeyboardButton("💎 Premium lo — Ads Skip Karo!", callback_data="show_premium")
]])
)
return
elif not AD_SYSTEM_ENABLED and s.get("shortlink_enabled", True):
all_done, next_sl, _ = await get_user_verify_state(uid)
if not all_done:
me_obj = await client.get_me()
short = None; sl_label = "Verify"; hours = 24
if next_sl:
sl_id = str(next_sl["_id"])
sl_label = next_sl.get("label", "Verify")
hours = next_sl.get("hours", 24)
token = await make_token(uid, f"sv_{sl_id}")
verify_url = f"https://t.me/{me_obj.username}?start=sv_{uid}_{token}_{sl_id}"
short = await get_cached_shortlink(uid, 0, verify_url, next_sl)
elif SHORTLINK_API:
token = await make_token(uid, "sv_env")
verify_url = f"https://t.me/{me_obj.username}?start=sv_{uid}_{token}"
try:
api_url = f"https://{SHORTLINK_URL}/api?api={SHORTLINK_API}&url={verify_url}&format=text"
async with aiohttp.ClientSession() as sess:
async with sess.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as r:
result = (await r.text()).strip()
short = result if result.startswith("http") else verify_url
except:
short = verify_url
else:
all_done = True
if not all_done and short:
await users_col.update_one(
{"user_id": uid},
{"$set": {"pending_file_id": msg_id, "pending_file_chat": 0}},
upsert=True
)
await message.reply(
f"🔐 **File Lene Se Pehle Ek Step!**\n\n"
f"**Steps follow karo:**\n"
f"1️⃣ Neeche **LINK** button dabao\n"
f"2️⃣ Page pe steps complete karo\n"
f"3️⃣ Bot pe wapas aao — file seedha milegi! 🎉\n\n"
f"⏰ Har **{hours} ghante** mein ek baar\n"
f"💎 Premium = kabhi verify nahi!",
reply_markup=InlineKeyboardMarkup([
[InlineKeyboardButton(f"🔗 {sl_label} — Verify Karo", url=short)],
[InlineKeyboardButton("❓ Verify Kaise Kare?", url=HOW_TO_VERIFY)],
[InlineKeyboardButton("💎 Premium lo — No Verify!", callback_data="show_premium")],
])
)
return
# STEP 2: Force Sub
if not prem:
joined, not_joined = await check_member_multi(uid, prem)
if not joined:
kb = await build_fsub_keyboard(not_joined, uid)
names = ", ".join(ch.get("title", "Channel") for ch in not_joined)
await message.reply(
f"📢 **Pehle Join Karo!**\n\n**{names}** join karo — phir file milegi! ✅",
reply_markup=kb
)
return
# STEP 3: Daily limit
if not prem:
count = await get_daily_count(uid)
if count >= s.get("daily_limit", 10):
await message.reply(
f"⚠️ **Daily Limit Khatam!**\n💎 /premium lo unlimited ke liye!"
)
return
# STEP 4: Send file
wait = await message.reply("⚡ File prepare ho rahi hai... ⏳\n_Thodi der mein PM mein milegi!_")
success, info = await send_file_to_pm(client, message.from_user, msg_id, prem)
await wait.delete()
if not success:
await message.reply(f"❌ File nahi aa payi.\nError: `{info}`")
return
# ── Normal /start ──
me = await client.get_me()
miniapp_url = _miniapp_url(uid, getattr(message.from_user,'first_name',None) or 'User')
buttons = [
[
InlineKeyboardButton("📢 Channel", url=f"https://t.me/{FORCE_SUB_CHANNEL.replace('@','')}"),
InlineKeyboardButton("➕ Group Add", url=f"https://t.me/{me.username}?startgroup=true")
],
[
InlineKeyboardButton("💎 Premium", callback_data="show_premium"),
InlineKeyboardButton("ℹ️ Help", callback_data="help")
],
[
InlineKeyboardButton("📊 My Stats", callback_data="my_stats"),
InlineKeyboardButton("🔗 Refer & Earn", callback_data="refer_info")
],
[
InlineKeyboardButton("🆘 Support", url=SUPPORT_LINK)
]
]
if miniapp_url:
buttons.append([InlineKeyboardButton("🌐 Mini App Kholo", web_app=WebAppInfo(url=miniapp_url))])
# Funny/Roast personality greetings
greetings = [
f"🔥 **AsBhai Drop Bot** 🔥\n\n"