From e920d4925a051d548c9786a7a95463cf75e5e353 Mon Sep 17 00:00:00 2001 From: Ace Date: Wed, 1 Jul 2026 12:15:37 +0200 Subject: [PATCH] Updated the to use targeted jobs but its weird --- browser-use-service/main.py | 408 +++++++++++++++++----- data/ai/jobs.jsonl | 12 +- src/app/(dashboard)/ai-assistant/page.tsx | 46 ++- src/components/ai/ai-chat.tsx | 18 +- src/components/ai/job-selector.tsx | 84 +++-- 5 files changed, 434 insertions(+), 134 deletions(-) diff --git a/browser-use-service/main.py b/browser-use-service/main.py index b24f6b9..78367a2 100644 --- a/browser-use-service/main.py +++ b/browser-use-service/main.py @@ -341,6 +341,7 @@ OFFER_PATTERNS = [ r"\bfree\s+domain\b", r"\bweb\s+hosting\b", r"\bfree\s+website\b", + r"\bfree\s+\w+\s+website\b", r"\bprofessional\s+website", r"\baffordable\s+web\b", r"\bget\s+a\s+free\b", @@ -365,6 +366,47 @@ OFFER_PATTERNS = [ r"\bsponsored\b", r"\bpromoted\b", r"\badvertisement\b", + r"\bdo\s+you\s+(need|want)\b", + r"\bwe\s+(can|will)\s+(build|design|create|develop|do)\b", + r"\bi\s+(can|will)\s+(build|design|create|develop|do)\b", + r"\bmy\s+portfolio\b", + r"\breasonable\s+(price|pricing|rate)\b", + r"\bwhatsapp\s+(me|us|at)\b", + r"\blooking\s+for\s+(a\s+)?(business|client|customer)\b", + r"\bhelp\s+your\s+business\b", + r"\bi\s+am\s+a\s+(web|freelance|designer|developer)\b", + r"\bcontact\s+me\b", + r"\bwe\s+(are|offer|provide)\s+(web|website|design|service)\b", + r"\btake\s+(the\s+|this\s+)?quiz\b", + r"\bhomeschool\b", + r"\byour\s+(home\s+)?tutor\b", + r"\blink\s+(in\s+)?(bio|comment|below)\b", + r"\bapply\s+now\b", + r"\bget\s+started\b", + r"\bfor\s+only\b", + r"\blow\s+(price|cost|rate)\b", + r"\bhit\s+me\s+up\b", + r"\bsend\s+me\s+(a\s+)?(message|dm|pm)\b", + r"\bi\s+do\s+(web|website|design|development)\b", + r"\bwe\s+do\s+(web|website|design|development)\b", + r"\bi'm\s+offering\b", + r"\bwe're\s+offering\b", + r"\bstarting\s+a\s+\w+\s+(service|business)\b", + r"\blooking\s+for\s+a\s+few\s+businesses?\b", + r"\blet('?)s\s+connect\b", + r"\bi\s+have\s+projects?\b", + r"\byour\s+business\b", + r"\bwordpress\s+development\b", + r"\be.?commerce\s+website\b", + r"\bwebsite\s+builder\b", + r"\bai\s+studio\b", + r"\bpage\s+speed\b", + r"\bguest\s+post", + r"\bguest\s+blog", + r"\bfor\s+sale\b", + r"\bselling\s+(my|a|the|this)\b", + r"\bpremium\b", + r"\bi'm\s+selling\b", ] REQUEST_PATTERNS = [ @@ -549,6 +591,48 @@ FB_SEARCHES = [ "want to build a website for my", ] +TUTORING_SEARCHES = [ + "need a tutor", + "looking for a tutor", + "tutor for my child", + "need help with homework", + "private tutor needed", + "looking for tutoring", + "need a math tutor", + "english tutor needed", + "online tutor for", + "lessons for my child", + "help my child with", + "need someone to tutor", + "looking for someone to teach", + "tutoring for my", + "need help learning", + "recommend a tutor", + "anyone know a tutor", + "need a private tutor", + "tutoring services for my child", + "need academic help", + "need a reading tutor", + "looking for piano teacher", + "need help with maths", + "need a science tutor", + "looking for language tutor", + "need programming tutor", + "coding tutor for my", + "sat tutor needed", + "exam preparation tutor", + "need a homeschool tutor", + "tutor near me for", +] + +def _search_list_for_query(query: str) -> list[str]: + """Pick the appropriate search query pool based on the search term.""" + tl = query.lower() + tutoring_terms = ["tutor", "tutoring", "lessons", "homework", "teach", "learning", "child", "math", "english", "science", "exam", "homeschool", "coding", "programming", "piano", "reading"] + if any(t in tl for t in tutoring_terms): + return TUTORING_SEARCHES + return FB_SEARCHES + VIEWPORTS = [ {'width': 1280, 'height': 800}, {'width': 1366, 'height': 768}, @@ -663,6 +747,9 @@ def _clean_fb_text(text: str) -> str: cleaned_lines.append(stripped) return '\n'.join(cleaned_lines) +# Words that should never be treated as person names +_NON_NAMES = {"online","tutor","tutoring","school","academy","learning","urgently","looking","south","africa","philippines","australia","creative","professional","digital","services","solutions","statistics","actuarial","homeschooling","homeschool","reading","math","english","science","grade","group","page","website","design","development","agency","company","studio","graphic","technical","support","customer","guest","post","fashion","wordpress","shopify","ecommerce","business","marketing","social","media","content","strategy","seo","free","domain","hosting","sponsored","promoted","advertisement","need","want","help","please","contact","send","message","whatsapp","hire","freelance","writer","blogger","expert","specialist","consultant","freelancer","software","creators","village","town","city","university","college","institute","evolve","aesthetics","sale","selling","premium","builder","pro","people","ecommerce"} + # ── Post Extraction ────────────────────────────────────────────────── # Two strategies for extracting posts from page content: # 1. _extract_posts_from_elements — uses structured DOM data from _get_article_elements() @@ -720,6 +807,25 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: posts = [] seen_texts = set() cur = [] + + def _find_author(candidate_lines: list[str]) -> str: + for line in reversed(candidate_lines): + ln = line.strip() + if not ln or len(ln) > 60 or len(ln) < 3: + continue + words = ln.split() + if not (1 < len(words) < 8): + continue + if not all(w[0].isalpha() and w[0].isupper() for w in words if w): + continue + lower_ln = ln.lower() + if any(kw in lower_ln for kw in BROAD_KEYWORDS) or is_offer(ln): + continue + if 'http' in ln or '/' in ln or '@' in ln: + continue + return ln + return '' + for l in lines: words = re.findall(r'[A-Za-z]{2,}', l) if len(words) < 2: @@ -735,7 +841,7 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: posts.append({ "title": snippet[:300], "content": snippet[:1000], - "author": '', + "author": _find_author(cur[-3:]), "url": url, "source": "facebook", "date": _parse_fb_date(cur + [l]), @@ -759,7 +865,40 @@ def _extract_posts_from_text(raw: str, url: str) -> list[dict]: cur.append(l) if len(cur) > 10: cur.pop(0) - return posts + # Post-process: scan each post's text for inline names + def _scan_inline_name(text: str) -> str: + for m in re.finditer(r'([A-Z][a-z]{2,}(?:\s+[A-Z][a-z]{2,}){1,3})[\s,;:]', text): + cand = m.group(1).strip() + if cand and 3 <= len(cand) <= 60: + lower = cand.lower() + words = lower.split() + if not any(kw in lower for kw in BROAD_KEYWORDS) and not is_offer(cand): + if not any(w in _NON_NAMES for w in words): + return cand + return '' + for p in posts: + if not p.get('author'): + txt = p.get('content') or p.get('title') or '' + name = _scan_inline_name(txt) + if name: + p['author'] = name + # Remove group posts: any post whose text looks like it came from a group + _group_suffixes = [" - South Africa", " - Australia", " PH", "Group", "help group", "info group"] + filtered = [] + for p in posts: + t = (p.get('title') or p.get('content') or '') + if ' / ' in t: + continue + lower = t.lower() + skip = False + for suff in _group_suffixes: + if suff.lower() in lower: + skip = True + break + if skip: + continue + filtered.append(p) + return filtered # ── Human-like Behavior Simulation ────────────────────────────────── # These functions add random delays, mouse movements, and scroll patterns @@ -811,58 +950,31 @@ async def _get_article_elements(page) -> list[dict]: return await page.evaluate('''() => { const results = []; const seenTexts = new Set(); - const selectors = [ - 'div[role="article"]', - 'div[role="feed"] > div', - 'div.x1yztbdb', - 'div[data-pagelet]', - ]; - for (const sel of selectors) { - const els = document.querySelectorAll(sel); - for (const el of els) { - const text = (el.innerText || '').trim(); - if (text.length < 40) continue; - const key = text.substring(0, 80); - if (seenTexts.has(key)) continue; - seenTexts.add(key); - - // --- Publisher name --- - let author = ''; - const nameEl = el.querySelector('h4, strong, a[role="link"]'); - if (nameEl) { - author = (nameEl.innerText || '').trim(); - } - if (!author) { - const links = el.querySelectorAll('a'); - for (const a of links) { - const t = (a.innerText || '').trim(); - if (t && t.length > 1 && t.length < 60 && /^[A-Za-zÀ-ÿ]/.test(t) && !t.includes('facebook') && !t.includes('/')) { - author = t; - break; - } - } - } - - // --- Post URL --- - let postUrl = ''; - for (const a of el.querySelectorAll('a')) { - const h = (a.getAttribute('href') || ''); - if (h.includes('/posts/') || h.includes('/photo/') || h.includes('/video/') || h.includes('/groups/') || h.includes('/permalink/')) { - postUrl = h.startsWith('http') ? h : 'https://www.facebook.com' + h; - break; - } - } - - // --- Date from