from concurrent.futures import ThreadPoolExecutor, as_completed from http.cookiejar import CookieJar from .logger import logger try: import browser_cookie3 as bc3 HAS_BC3 = True except ImportError: bc3 = None HAS_BC3 = False def load_browser_cookies(domain_name: str = "", verbose: bool = False) -> dict: """ Try to load cookies from all supported browsers and return combined cookiejar. Optionally pass in a domain name to only load cookies from the specified domain. Parameters ---------- domain_name : str, optional Domain name to filter cookies by, by default will load all cookies without filtering. verbose : bool, optional If `True`, will print more infomation in logs. Returns ------- `dict[str, dict]` Dictionary with browser as keys and their cookies for the specified domain as values. Only browsers that have cookies for the specified domain will be included. """ if not HAS_BC3 or bc3 is None: if verbose: logger.debug( "Optional dependency 'browser-cookie3' not found. Skipping browser cookie loading." ) return {} browser_fns = [ bc3.chrome, bc3.chromium, bc3.opera, bc3.opera_gx, bc3.brave, bc3.edge, bc3.vivaldi, bc3.firefox, bc3.librewolf, bc3.safari, ] cookies = {} def fetch_cookies(cookie_fn): try: jar: CookieJar = cookie_fn(domain_name=domain_name) if jar: return cookie_fn.__name__, {cookie.name: cookie.value for cookie in jar} except bc3.BrowserCookieError: pass except PermissionError: if verbose: logger.warning( f"Permission denied while trying to load cookies from {cookie_fn.__name__}." ) except Exception: if verbose: logger.debug( f"Failed to load cookies from {cookie_fn.__name__} (may not be installed)." ) return None with ThreadPoolExecutor(max_workers=len(browser_fns)) as executor: futures = [executor.submit(fetch_cookies, fn) for fn in browser_fns] for future in as_completed(futures): result = future.result() if result: browser_name, cookie_dict = result cookies[browser_name] = cookie_dict return cookies