File size: 28,014 Bytes
ece3e8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import time
import random
import os
import json
from playwright.sync_api import sync_playwright

from playwright_stealth import stealth_sync
from bot_features import wait_for_otp

def human_scroll(page):
    """Simulates a human scrolling up and down the page while reading."""
    scroll_amount = random.randint(300, 700)
    page.mouse.wheel(0, scroll_amount)
    time.sleep(random.uniform(1.0, 2.5))
    page.mouse.wheel(0, -random.randint(100, 300))
    time.sleep(random.uniform(0.5, 1.5))

def human_distraction(context):
    """Simulates a human getting distracted by opening a new tab, browsing, and coming back."""
    if random.random() < 0.15:  # 15% chance
        print("[Instahyre Stealth] User got distracted. Opening a new tab...")
        distraction_page = context.new_page()
        try:
            distraction_page.goto("https://www.google.com", timeout=10000)
            time.sleep(random.uniform(2, 5))
            distraction_page.type("textarea[name='q']", "software engineering news", delay=random.randint(50, 150))
            distraction_page.keyboard.press("Enter")
            time.sleep(random.uniform(3, 8))
        except Exception:
            pass
        finally:
            distraction_page.close()
            time.sleep(random.uniform(1, 2))

def coffee_break():
    """Simulates the user stepping away from the computer or checking their phone."""
    if random.random() < 0.10:  # 10% chance
        break_time = random.randint(25, 45)
        print(f"[Instahyre Stealth] Taking a coffee break for {break_time} seconds...")
        time.sleep(break_time)

def human_text_highlighting(page):
    """Simulates a human dragging their mouse to highlight text while reading."""
    try:
        width = page.viewport_size['width']
        height = page.viewport_size['height']
        start_x = random.randint(200, width - 200)
        start_y = random.randint(200, height - 200)
        
        page.mouse.move(start_x, start_y)
        page.mouse.down()
        time.sleep(random.uniform(0.1, 0.4))
        page.mouse.move(start_x + random.randint(50, 300), start_y + random.randint(10, 50), steps=10)
        time.sleep(random.uniform(0.5, 1.5))
        page.mouse.up()
        
        # Click somewhere to un-highlight
        time.sleep(random.uniform(1, 3))
        page.mouse.click(start_x - 50, start_y - 50)
    except Exception:
        pass

def human_mouse_wandering(page):
    """Randomly moves the mouse around the screen to simulate a bored or reading human."""
    try:
        width = page.viewport_size['width']
        height = page.viewport_size['height']
        for _ in range(random.randint(2, 4)):
            x = random.randint(100, width - 100)
            y = random.randint(100, height - 100)
            page.mouse.move(x, y, steps=random.randint(10, 25))
            time.sleep(random.uniform(0.2, 0.7))
    except Exception:
        pass

def human_typing_with_mistakes(page, selector, text):
    """Types text like a human, occasionally making a typo and using backspace to correct it."""
    page.click(selector)
    time.sleep(random.uniform(0.2, 0.8))
    for char in text:
        # 5% chance to make a typo
        if random.random() < 0.05:
            wrong_char = random.choice("abcdefghijklmnopqrstuvwxyz")
            page.keyboard.press(wrong_char)
            time.sleep(random.uniform(0.1, 0.3))
            page.keyboard.press("Backspace")
            time.sleep(random.uniform(0.2, 0.5))
        
        page.keyboard.press(char)
        time.sleep(random.uniform(0.05, 0.18))

def send_instahyre_results_telegram(applied_jobs_list, total_found, success):
    """Send Instahyre results to Telegram as a consolidated message."""
    try:
        import telebot
        bot_token = os.getenv("TELEGRAM_TOKEN") or os.getenv("TELEGRAM_BOT_TOKEN")
        chat_id = os.getenv("TELEGRAM_CHAT_ID")
        if not chat_id:
            chat_id_file = "chat_id.json"
            if os.path.exists(chat_id_file):
                try:
                    with open(chat_id_file, "r") as f:
                        data = json.load(f)
                        chat_id = data.get("chat_id")
                except: pass
        
        if not bot_token or not chat_id:
            return
        
        tg_bot = telebot.TeleBot(bot_token, parse_mode=None)
        
        status_icon = "βœ…" if success else "⚠️"
        applied_count = len(applied_jobs_list)
        
        msg = (
            f"{status_icon} *Instahyre Campaign Results*\n"
            f"━━━━━━━━━━━━━━━━━━━━━━\n\n"
            f"πŸ“Š *Summary:*\n"
            f"  πŸ” Jobs Found: *{total_found}*\n"
            f"  βœ… Successfully Applied: *{applied_count}*\n\n"
        )
        
        if applied_jobs_list:
            msg += "πŸ’Ό *Applied To:*\n"
            for i, job in enumerate(applied_jobs_list[:20]):
                title = job.get("title", "Unknown")[:40]
                company = job.get("company", "Unknown")[:25]
                msg += f"  {i+1}. {title} β€” _{company}_\n"
            if len(applied_jobs_list) > 20:
                msg += f"  _...+{len(applied_jobs_list)-20} more_\n"
        
        msg += (
            f"\n━━━━━━━━━━━━━━━━━━━━━━\n"
            f"_Instahyre engine completed. Next auto-run at 11 PM._"
        )
        
        tg_bot.send_message(chat_id, msg, parse_mode="Markdown")
    except Exception as e:
        print(f"[Instahyre] Telegram notification failed: {e}")

def run_instahyre_mass_apply(skills="Software Engineer Fresher", max_applications=20, email=None, password=None):
    """

    Autonomous engine to log into Instahyre and mass-apply to jobs using human-mimicking behavior.

    Returns (success: bool, message: str, applied_jobs: list)

    """
    # Credentials from params or environment
    if not email:
        email = os.getenv("INSTAHYRE_EMAIL", "manojprofessional007@gmail.com")
    if not password:
        password = os.getenv("INSTAHYRE_PASSWORD")
    
    if not password:
        return False, "Instahyre password not set. Use `/instahyre your_email | your_password` in Telegram.", []
    
    print(f"[Instahyre] Starting Stealth Mass-Applier Engine for {email}...")
    applied_count = 0
    applied_jobs_list = []
    total_jobs_found = 0
    
    with sync_playwright() as p:
        is_huggingface = "SPACE_ID" in os.environ
        # Force headless=False to bypass Cloudflare Turnstile (xvfb handles it on Hugging Face)
        browser = p.chromium.launch(headless=False, args=['--no-sandbox', '--disable-blink-features=AutomationControlled'])
        auth_file = "instahyre_auth.json"
        has_auth = os.path.exists(auth_file)
        
        context_args = {
            "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
            "viewport": {'width': 1920, 'height': 1080}
        }
        if has_auth:
            context_args["storage_state"] = auth_file
            
        context = browser.new_context(**context_args)
        page = context.new_page()
        stealth_sync(page)
        
        # --- ENHANCEMENT: Advanced Stealth Injection ---
        # Bypasses Cloudflare Turnstile and Datadome fingerprinting
        page.add_init_script("""

            Object.defineProperty(navigator, 'webdriver', { get: () => undefined });

            window.navigator.chrome = { runtime: {} };

            Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });

            Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] });

            const originalQuery = window.navigator.permissions.query;

            window.navigator.permissions.query = (parameters) => (

                parameters.name === 'notifications' ?

                    Promise.resolve({ state: Notification.permission }) :

                    originalQuery(parameters)

            );

        """)

        try:
            # 0. Check Session
            needs_login = True
            if has_auth:
                print("[Instahyre] Found saved session. Checking if it's still valid...")
                page.goto("https://www.instahyre.com/candidate/opportunities/", timeout=60000)
                time.sleep(random.uniform(3, 5))
                
                # Cloudflare check on session opportunities page
                for _ in range(4): # Check up to 4 times (40 seconds max)
                    try:
                        page_text = page.locator("body").inner_text(timeout=2000).lower()
                        if "just a moment" in page_text or "cloudflare" in page_text or "security check" in page_text or "verify you are human" in page_text:
                            print("[Instahyre] Cloudflare protection detected on opportunities page! Waiting 10s for it to resolve...")
                            time.sleep(10)
                            human_mouse_wandering(page)
                        else:
                            break
                    except: pass
                
                # Verify we are on opportunities and body text has dashboard characteristics
                try:
                    body_text = page.locator("body").inner_text(timeout=3000).lower()
                    is_logged_in = (
                        "login" not in page.url.lower() and 
                        ("opportunity" in body_text or "job" in body_text or "apply" in body_text or "profile" in body_text or "sign out" in body_text)
                    )
                except:
                    is_logged_in = False
                    
                if is_logged_in:
                    print("[Instahyre] Session is valid! Skipping login.")
                    needs_login = False
                else:
                    print("[Instahyre] Session is invalid or blocked. Navigating to login...")

            if needs_login:
                # 1. Login
                print("[Instahyre] Navigating to login page...")
                page.goto("https://www.instahyre.com/login/", timeout=60000)
                time.sleep(random.uniform(3, 5))
                
                # Cloudflare check
                for _ in range(4): # Check up to 4 times (40 seconds max)
                    try:
                        page_text = page.locator("body").inner_text(timeout=2000).lower()
                        if "just a moment" in page_text or "cloudflare" in page_text or "security check" in page_text or "verify you are human" in page_text:
                            print("[Instahyre] Cloudflare protection detected! Waiting 10s for it to resolve...")
                            time.sleep(10)
                            human_mouse_wandering(page)
                        else:
                            break
                    except: pass

                # Human-like typing with mistakes
                human_mouse_wandering(page)
                
                # Try multiple selectors for email field
                email_selectors = ["input[name='email']", "input[type='email']", "input[placeholder*='email']", "#email", "input.email-input"]
                email_filled = False
                for sel in email_selectors:
                    try:
                        el = page.locator(sel).first
                        if el.is_visible(timeout=3000):
                            el.hover()
                            el.click()
                            time.sleep(random.uniform(0.5, 1.0))
                            human_typing_with_mistakes(page, sel, email)
                            email_filled = True
                            break
                    except: continue
                
                if not email_filled:
                    return False, "Could not find email input field on Instahyre login page.", []
                
                time.sleep(random.uniform(1, 2))
                
                # Try multiple selectors for password field
                human_mouse_wandering(page)
                pwd_selectors = ["input[name='password']", "input[type='password']", "input[placeholder*='password']", "#password"]
                pwd_filled = False
                for sel in pwd_selectors:
                    try:
                        el = page.locator(sel).first
                        if el.is_visible(timeout=3000):
                            el.hover()
                            el.click()
                            time.sleep(random.uniform(0.5, 1.0))
                            human_typing_with_mistakes(page, sel, password)
                            pwd_filled = True
                            break
                    except: continue
                
                if not pwd_filled:
                    return False, "Could not find password input field on Instahyre login page.", []
                
                time.sleep(random.uniform(1.5, 3))
                
                print("[Instahyre] Clicking login...")
                # Try multiple submit button selectors
                submit_selectors = ["button[type='submit']", "input[type='submit']", "button:has-text('Login')", "button:has-text('Sign In')", ".login-btn", ".btn-primary"]
                for sel in submit_selectors:
                    try:
                        btn = page.locator(sel).first
                        if btn.is_visible(timeout=2000):
                            btn.hover()
                            time.sleep(random.uniform(0.5, 1.2))
                            btn.click()
                            break
                    except: continue
                
                page.wait_for_load_state("networkidle", timeout=30000)
                time.sleep(random.uniform(3, 5))
                
                # Auto-OTP Handling (If Instahyre asks for email verification)
                otp_detected = False
                try:
                    page_text = page.locator("body").inner_text(timeout=5000).lower()
                    otp_detected = (
                        "verify" in page.url or 
                        "verification" in page_text or
                        "otp" in page_text or
                        "enter code" in page_text
                    )
                except: pass
                
                if otp_detected:
                    print("[Instahyre] OTP Verification Detected! Booting up IMAP Sniper...")
                    bot_email_pass = os.getenv("BOT_EMAIL_PASSWORD")
                    if bot_email_pass:
                        otp_code = wait_for_otp(email, bot_email_pass, timeout_seconds=90)
                        if otp_code:
                            # Try to find the OTP input field
                            otp_selectors = [
                                "input[placeholder*='OTP']", "input[placeholder*='otp']",
                                "input[placeholder*='code']", "input[placeholder*='Code']",
                                "input[name*='code']", "input[name*='otp']",
                                "input[type='text']", "input[type='number']"
                            ]
                            for sel in otp_selectors:
                                try:
                                    el = page.locator(sel).first
                                    if el.is_visible(timeout=2000):
                                        human_typing_with_mistakes(page, sel, otp_code)
                                        break
                                except: continue
                            
                            page.keyboard.press("Enter")
                            page.wait_for_load_state("networkidle")
                            time.sleep(random.uniform(3, 5))
                        else:
                            return False, "Login failed. OTP Verification required but sniper timed out.", []
                    else:
                        return False, "Login failed. OTP required but BOT_EMAIL_PASSWORD is not set in env.", []
                
                # Check if login was successful
                current_url = page.url.lower()
                if "login" in current_url and "verify" not in current_url and "dashboard" not in current_url:
                    return False, "Login failed. Please check your Instahyre password in environment variables.", []
                    
                print("[Instahyre] Login successful! Saving session and navigating to jobs...")
                try:
                    context.storage_state(path=auth_file)
                except Exception as e:
                    print(f"[Instahyre] Could not save session state: {e}")
                
            # End of login block - indent the rest properly if needed.
            # But here the search jobs block just continues in the same scope, which is fine since both paths (needs_login or not) end up here.

            # 2. Search Jobs β€” try multiple search URL patterns
            search_urls = [
                "https://www.instahyre.com/candidate/opportunities/",
                "https://www.instahyre.com/search-jobs/",
            ]
            
            jobs_page_loaded = False
            for search_url in search_urls:
                try:
                    print(f"[Instahyre] Trying search URL: {search_url}")
                    page.goto(search_url, timeout=60000)
                    page.wait_for_load_state("networkidle")
                    time.sleep(random.uniform(3, 6))
                    
                    # Check if page has job listings
                    body_text = page.locator("body").inner_text(timeout=5000)
                    if len(body_text) > 200 and ("opportunity" in body_text.lower() or "job" in body_text.lower() or "apply" in body_text.lower()):
                        jobs_page_loaded = True
                        print(f"[Instahyre] Job listings found on: {search_url}")
                        break
                except Exception as e:
                    print(f"[Instahyre] Search URL failed: {e}")
                    continue
            
            if not jobs_page_loaded:
                # Take screenshot for debugging
                try:
                    page.screenshot(path="instahyre_debug.png")
                except: pass
                return False, f"Could not load job listings. Current page: {page.url}", []
            
            # 3. Apply Loop β€” try multiple selectors for job cards
            while applied_count < max_applications:
                # Find all job cards using multiple selector strategies
                job_card_selectors = [
                    ".employer-details",
                    ".opportunity-card",
                    "[class*='opportunity']",
                    "[class*='job-card']",
                    ".card-body",
                    ".job-listing",
                    "div[data-opportunity-id]",
                ]
                
                job_cards = []
                for sel in job_card_selectors:
                    try:
                        cards = page.locator(sel).all()
                        if cards and len(cards) > 0:
                            job_cards = cards
                            print(f"[Instahyre] Found {len(cards)} jobs using selector: {sel}")
                            break
                    except: continue
                
                if not job_cards:
                    # Try getting all clickable elements that look like job entries
                    print("[Instahyre] No job cards found with known selectors. Trying generic approach...")
                    try:
                        # Look for any link or card that contains job-related text
                        all_links = page.locator("a[href*='opportunity'], a[href*='job'], div.card").all()
                        if all_links:
                            job_cards = all_links
                            print(f"[Instahyre] Found {len(all_links)} potential job elements via generic approach.")
                    except: pass
                
                if not job_cards:
                    print("[Instahyre] No more jobs found on this page.")
                    break
                
                total_jobs_found += len(job_cards)
                print(f"[Instahyre] Processing {len(job_cards)} jobs on current page.")
                
                for card in job_cards:
                    if applied_count >= max_applications:
                        break
                        
                    try:
                        human_mouse_wandering(page)
                        
                        # 15% chance to get distracted by another tab
                        human_distraction(context)
                        
                        # Scroll to the card to simulate human reading
                        card.scroll_into_view_if_needed()
                        human_scroll(page)
                        
                        # Extract job title/company before clicking
                        job_title = "Unknown"
                        job_company = "Unknown"
                        try:
                            card_text = card.inner_text(timeout=3000)
                            lines = [l.strip() for l in card_text.split('\n') if l.strip()]
                            if lines:
                                job_title = lines[0][:50]
                                if len(lines) > 1:
                                    job_company = lines[1][:30]
                        except: pass
                        
                        # Randomly highlight text on the screen while reading
                        if random.random() < 0.3:
                            human_text_highlighting(page)
                            
                        # Hover over the specific job card before clicking
                        card.hover()
                        time.sleep(random.uniform(0.8, 1.5))
                        card.click()
                        time.sleep(random.uniform(3, 5))
                        
                        human_mouse_wandering(page)
                        human_scroll(page)
                        
                        # Look for the Apply button in the popup/details view β€” try multiple selectors
                        apply_selectors = [
                            "button:has-text('Apply')",
                            "button:has-text('Interested')",
                            "button:has-text('I am interested')",
                            "a:has-text('Apply')",
                            ".apply-btn",
                            "[class*='apply']",
                            "button.btn-primary:has-text('Apply')",
                        ]
                        
                        applied = False
                        for apply_sel in apply_selectors:
                            try:
                                apply_btn = page.locator(apply_sel).first
                                if apply_btn.is_visible(timeout=2000):
                                    apply_btn.hover()
                                    time.sleep(random.uniform(1.0, 2.5))
                                    
                                    # Randomly click near the button before clicking the button (simulating hesitation)
                                    try:
                                        box = apply_btn.bounding_box()
                                        if box:
                                            page.mouse.click(box['x'] - 20, box['y'] - 20)
                                            time.sleep(random.uniform(0.5, 1.5))
                                    except: pass
                                    
                                    apply_btn.click()
                                    applied_count += 1
                                    applied_jobs_list.append({
                                        "title": job_title,
                                        "company": job_company,
                                    })
                                    print(f"[Instahyre] βœ… Successfully applied to job #{applied_count}: {job_title}")
                                    applied = True
                                    time.sleep(random.uniform(5, 10)) # Human reading the success message
                                    
                                    # 10% chance to take a long coffee break after an application
                                    coffee_break()
                                    break
                            except: continue
                        
                        if not applied:
                            print(f"[Instahyre] ⏭ Already applied or Apply button missing for: {job_title}. Skipping.")
                            
                        # Close popup or go back if necessary
                        page.keyboard.press("Escape")
                        time.sleep(random.uniform(1, 2))
                        
                    except Exception as e:
                        print(f"[Instahyre] Error clicking job card: {e}")
                        page.keyboard.press("Escape")
                        time.sleep(1)
                
                if applied_count >= max_applications:
                    break
                    
                # Go to next page β€” try multiple selectors
                next_selectors = [
                    "li.next a",
                    "a:has-text('Next')",
                    ".pagination .next",
                    "button:has-text('Next')",
                    "a[rel='next']",
                ]
                next_found = False
                for next_sel in next_selectors:
                    try:
                        next_btn = page.locator(next_sel).first
                        if next_btn.is_visible(timeout=3000):
                            print("[Instahyre] Moving to next page...")
                            next_btn.click()
                            page.wait_for_load_state("networkidle")
                            time.sleep(random.uniform(3, 6))
                            next_found = True
                            break
                    except: continue
                
                if not next_found:
                    print("[Instahyre] No next page button found. Search complete.")
                    break

            result_msg = f"Successfully applied to {applied_count} out of {total_jobs_found} jobs found on Instahyre!"
            
            # Send results to Telegram
            send_instahyre_results_telegram(applied_jobs_list, total_jobs_found, applied_count > 0)
            
            return applied_count > 0, result_msg, applied_jobs_list
            
        except Exception as e:
            error_msg = f"Engine crashed: {str(e)}"
            # Send error to Telegram
            send_instahyre_results_telegram(applied_jobs_list, total_jobs_found, False)
            return False, error_msg, []
        finally:
            context.close()
            browser.close()

if __name__ == "__main__":
    run_instahyre_mass_apply(None, None, 'Software Engineer Fresher', 1)
    pass