# retoor <retoor@molodetz.nl>
import logging
import random
import re
from devplacepy.services.bot.config import (
FEED_TOPICS,
GIST_LANGUAGES,
PROJECT_TYPES,
SEARCH_TERMS,
)
logger = logging.getLogger(__name__)
class BotBrowseMixin:
async def _scroll_page(self) -> None:
await self.b.scroll(random.randint(300, 900))
self._log("Scrolled down")
async def _random_nav_click(self) -> bool:
b = self.b
nav_links = b.page.locator(".topnav-links a, .topnav a, a.topnav-link")
count = await nav_links.count()
if count == 0:
nav_links = b.page.locator(
"a[href='/feed'], a[href='/projects'], a[href='/messages'], a[href='/notifications']"
)
count = await nav_links.count()
if count == 0:
return False
picked = random.randrange(count)
try:
text = await nav_links.nth(picked).inner_text()
href = await nav_links.nth(picked).get_attribute("href")
await nav_links.nth(picked).click(timeout=5000)
await b._idle(1.0, 2.0)
self._log(f"Nav: {text.strip()[:30] if text else href}")
return True
except Exception as e:
logger.debug("_random_nav_click failed: %s", e)
return False
async def _browse_section(self) -> bool:
section, query = random.choice(
[
("feed", f"?tab={random.choice(['trending', 'recent', 'following'])}"),
("feed", f"?topic={random.choice(FEED_TOPICS)}"),
("projects", f"?type={random.choice(PROJECT_TYPES)}"),
("gists", f"?language={random.choice(GIST_LANGUAGES)}"),
]
)
await self.b.goto(f"{self.base_url}/{section}{query}")
self._action("BROWSE", f"/{section}{query}")
await self.b._idle(0.8, 1.8)
return True
async def _search(self, section: str = "feed") -> bool:
terms = list(
SEARCH_TERMS.get(self.state.persona, ["python", "rust", "ai", "web"])
)
for href in self.state.known_posts[-10:]:
slug = href.rsplit("/", 1)[-1]
words = [w for w in re.split(r"[-_]", slug) if len(w) > 4]
if words:
terms.append(random.choice(words))
term = random.choice(terms)
await self.b.goto(f"{self.base_url}/{section}?search={term.replace(' ', '+')}")
self.state.searches_run += 1
self._action("SEARCH", f"/{section} '{term}'")
await self.b._idle(0.8, 1.8)
return True
async def _engage_community(self) -> bool:
b = self.b
tab = random.choice(["recent", "recent", "trending"])
await b.goto(f"{self.base_url}/feed?tab={tab}")
await b._idle(0.8, 1.8)
links = b.page.locator("a[href*='/posts/']")
count = await links.count()
if count == 0:
return False
own = set(self.state.own_post_urls)
known = {u.lower() for u in self.state.known_users if u}
me = self.state.username.lower()
ranked: list[tuple[float, int, str]] = []
for i in range(min(count, 30)):
link = links.nth(i)
href = await link.get_attribute("href") or ""
if "/posts/" not in href or len(href) < 20:
continue
if any(href in u for u in own):
continue
score = random.random()
try:
card = link.locator(
"xpath=ancestor::*[contains(@class,'post-card') or self::article][1]"
).first
if await card.count() > 0:
author = card.locator("a[href*='/profile/']").first
if await author.count() > 0:
ah = await author.get_attribute("href") or ""
uname = (
ah.split("/profile/")[-1].split("?")[0].split("/")[0].lower()
)
if uname and uname == me:
continue
if uname in known:
score += 2.0
except Exception:
pass
ranked.append((score, i, href))
if not ranked:
return False
ranked.sort(key=lambda r: r[0], reverse=True)
idx, href = ranked[0][1], ranked[0][2]
try:
await links.nth(idx).click(timeout=5000)
except Exception as e:
logger.debug("_engage_community open failed: %s", e)
return False
await b._idle(1.0, 2.0)
if href and href not in self.state.known_posts:
self.state.known_posts.append(href)
await self._read_like_human(3, 60)
acted = False
if await self._comment_on_post():
acted = True
await b._idle(0.5, 1.5)
if random.random() < 0.6 and await self._reply_to_random_comment():
acted = True
await b._idle(0.4, 1.2)
if random.random() < 0.7:
await self._vote_on_feed()
if random.random() < 0.5:
await self._react_to_object()
if acted:
self._action("ENGAGE", f"community thread {href[-40:]}")
return acted