import os import requests from bs4 import BeautifulSoup from duckduckgo_search import DDGS class SimpleTextBrowser: def __init__( self, timeout: int = 10, user_agent: str | None = None, viewport_size: int = 5000, downloads_folder: str = "downloads", ): self.session = requests.Session() self.session.headers.update({"User-Agent": user_agent or "Mozilla/5.0"}) self.timeout = timeout self.viewport_size = viewport_size os.makedirs(downloads_folder, exist_ok=True) self.pages: list[str] = [""] self._idx = 0 def _split(self, text: str): self.pages = [ text[i : i + self.viewport_size] for i in range(0, len(text), self.viewport_size) ] self._idx = 0 def visit_page(self, url: str) -> str: """ Internal: fetch and split a page into viewports. """ resp = self.session.get(url, timeout=self.timeout) resp.raise_for_status() text = resp.text if "text/html" in resp.headers.get("content-type", ""): text = BeautifulSoup(text, "lxml").get_text("\n") self._split(text) return self.pages[self._idx] def search(self, query: str) -> str: """ Internal: DuckDuckGo search → snippets. """ results = list(DDGS().text(query, timelimit=None)) hits = "\n".join( f"{i+1}. {h['title']} — {h.get('snippet','')}" for i, h in enumerate(results) ) full = f"Search results for '{query}':\n\n" + hits self._split(full) return self.pages[self._idx] def page_down(self) -> str: self._idx = min(self._idx + 1, len(self.pages) - 1) return self.pages[self._idx] def page_up(self) -> str: self._idx = max(self._idx - 1, 0) return self.pages[self._idx] def find_on_page(self, search_string: str) -> str: full = "\n".join(self.pages).lower() pos = full.find(search_string.lower()) if pos < 0: return f"'{search_string}' not found." start = max(0, pos - 100) end = min(len(full), pos + 100) return full[start:end] def find_next(self) -> str: return "Use `find_on_page_ctrl_f` again with a new string to find further occurrences." # one shared browser instance: _BROWSER = SimpleTextBrowser()