|
# 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
|