Добавлен README.md с списком группы
This commit is contained in:
		
						commit
						fe513ea8b0
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					.env
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					**/__pycache__/
 | 
				
			||||||
							
								
								
									
										0
									
								
								driver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								driver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										97
									
								
								driver/chrome_proxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								driver/chrome_proxy.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChromeProxy:
 | 
				
			||||||
 | 
					    def __init__(self, host: str, port: int, username: str = "", password: str = ""):
 | 
				
			||||||
 | 
					        self.host = host
 | 
				
			||||||
 | 
					        self.port = port
 | 
				
			||||||
 | 
					        self.username = username
 | 
				
			||||||
 | 
					        self.password = password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_extension(self, name: str = "Chrome Proxy", version="1.0.0") -> str:
 | 
				
			||||||
 | 
					        proxy_folder = tempfile.mkdtemp(prefix="chrome_proxy_")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._create_manifest(
 | 
				
			||||||
 | 
					            name=name,
 | 
				
			||||||
 | 
					            version=version,
 | 
				
			||||||
 | 
					            proxy_folder=proxy_folder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self._create_background_js(proxy_folder)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return proxy_folder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _create_manifest(self, name, version, proxy_folder): 
 | 
				
			||||||
 | 
					        manifest = ChromeProxy.manifest_json
 | 
				
			||||||
 | 
					        manifest = manifest.replace("<ext_name>", name)
 | 
				
			||||||
 | 
					        manifest = manifest.replace("<ext_ver>", version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with open(f"{proxy_folder}/manifest.json", "w") as f:
 | 
				
			||||||
 | 
					            f.write(manifest)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def _create_background_js(self, proxy_folder): 
 | 
				
			||||||
 | 
					        js = ChromeProxy.background_js
 | 
				
			||||||
 | 
					        js = js.replace("<proxy_host>", self.host)
 | 
				
			||||||
 | 
					        js = js.replace("<proxy_port>", str(self.port))
 | 
				
			||||||
 | 
					        js = js.replace("<proxy_username>", self.username)
 | 
				
			||||||
 | 
					        js = js.replace("<proxy_password>", self.password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with open(f"{proxy_folder}/background.js", "w") as f:
 | 
				
			||||||
 | 
					            f.write(js)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    manifest_json = """
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        "version": "<ext_ver>",
 | 
				
			||||||
 | 
					        "manifest_version": 3,
 | 
				
			||||||
 | 
					        "name": "<ext_name>",
 | 
				
			||||||
 | 
					        "permissions": [
 | 
				
			||||||
 | 
					            "proxy",
 | 
				
			||||||
 | 
					            "tabs",
 | 
				
			||||||
 | 
					            "storage",
 | 
				
			||||||
 | 
					            "webRequest",
 | 
				
			||||||
 | 
					            "webRequestAuthProvider"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "host_permissions": [
 | 
				
			||||||
 | 
					            "<all_urls>"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "background": {
 | 
				
			||||||
 | 
					            "service_worker": "background.js"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "minimum_chrome_version": "22.0.0"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    background_js = """
 | 
				
			||||||
 | 
					    var config = {
 | 
				
			||||||
 | 
					        mode: "fixed_servers",
 | 
				
			||||||
 | 
					        rules: {
 | 
				
			||||||
 | 
					            singleProxy: {
 | 
				
			||||||
 | 
					                scheme: "http",
 | 
				
			||||||
 | 
					                host: "<proxy_host>",
 | 
				
			||||||
 | 
					                port: parseInt("<proxy_port>")
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            bypassList: ["localhost"]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chrome.proxy.settings.set({
 | 
				
			||||||
 | 
					        value: config,
 | 
				
			||||||
 | 
					        scope: "regular"
 | 
				
			||||||
 | 
					    }, function() {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function callbackFn(details) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            authCredentials: {
 | 
				
			||||||
 | 
					                username: "<proxy_username>",
 | 
				
			||||||
 | 
					                password: "<proxy_password>"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chrome.webRequest.onAuthRequired.addListener(
 | 
				
			||||||
 | 
					        callbackFn, {
 | 
				
			||||||
 | 
					            urls: ["<all_urls>"]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ['blocking']
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
							
								
								
									
										60
									
								
								driver/driver_creator.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								driver/driver_creator.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					import random
 | 
				
			||||||
 | 
					from driver.chrome_proxy import ChromeProxy
 | 
				
			||||||
 | 
					from urllib.parse import urlparse
 | 
				
			||||||
 | 
					import undetected_chromedriver as uc
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# В patcher.py библиотеки undetected_chromedriver
 | 
				
			||||||
 | 
					    # import secrets 
 | 
				
			||||||
 | 
					    # prefix = 'undetected' ----> prefix = secrets.token_hex(8)
 | 
				
			||||||
 | 
					# Использование случайного токена в качестве префикса гарантирует, 
 | 
				
			||||||
 | 
					# что каждый процесс будет иметь свою уникальную директорию, 
 | 
				
			||||||
 | 
					# даже если несколько процессов работают одновременн
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriverCreator:
 | 
				
			||||||
 | 
					    def __init__(self, proxy_list):
 | 
				
			||||||
 | 
					        self.proxy_list = proxy_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _switch_proxy(self):
 | 
				
			||||||
 | 
					        proxy = random.choice(self.proxy_list)
 | 
				
			||||||
 | 
					        parsed_proxy = urlparse(proxy)
 | 
				
			||||||
 | 
					        return ChromeProxy(
 | 
				
			||||||
 | 
					            parsed_proxy.hostname,
 | 
				
			||||||
 | 
					            parsed_proxy.port,
 | 
				
			||||||
 | 
					            parsed_proxy.username,
 | 
				
			||||||
 | 
					            parsed_proxy.password
 | 
				
			||||||
 | 
					        ).create_extension()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_driver(self):
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Отключает JS
 | 
				
			||||||
 | 
					        Каждый запрос получает `сырой` html
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        # extension_path = self._switch_proxy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        options = uc.ChromeOptions()
 | 
				
			||||||
 | 
					        # options.add_argument(f"--load-extension={extension_path}") # временно
 | 
				
			||||||
 | 
					        options.add_argument("--headless=new")
 | 
				
			||||||
 | 
					        options.add_argument("--disable-gpu")
 | 
				
			||||||
 | 
					        options.add_argument("--disable-dev-shm-usage")
 | 
				
			||||||
 | 
					        options.add_argument("--no-sandbox")
 | 
				
			||||||
 | 
					        options.add_argument("--disable-webgl")
 | 
				
			||||||
 | 
					        options.add_argument("--disable-software-rasterizer")
 | 
				
			||||||
 | 
					        # options.add_argument("--disable-extensions")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        prefs = {"profile.managed_default_content_settings.javascript": 2}
 | 
				
			||||||
 | 
					        options.experimental_options["prefs"] = prefs
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        driver = uc.Chrome(
 | 
				
			||||||
 | 
					            options=options,
 | 
				
			||||||
 | 
					            version_main=132,
 | 
				
			||||||
 | 
					            # user_multi_procs=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
 | 
				
			||||||
 | 
					            "source": "Object.defineProperty(navigator, 'javaEnabled', {get: () => false});"
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        driver.execute_cdp_cmd("Emulation.setScriptExecutionDisabled", {"value": True})
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return driver
 | 
				
			||||||
							
								
								
									
										402
									
								
								patcher.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								patcher.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,402 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					# this module is part of undetected_chromedriver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from distutils.version import LooseVersion
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pathlib
 | 
				
			||||||
 | 
					import platform
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					import string
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					from urllib.request import urlopen
 | 
				
			||||||
 | 
					from urllib.request import urlretrieve
 | 
				
			||||||
 | 
					import zipfile
 | 
				
			||||||
 | 
					from multiprocessing import Lock
 | 
				
			||||||
 | 
					import secrets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Patcher(object):
 | 
				
			||||||
 | 
					    lock = Lock()
 | 
				
			||||||
 | 
					    exe_name = "chromedriver%s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    platform = sys.platform
 | 
				
			||||||
 | 
					    if platform.endswith("win32"):
 | 
				
			||||||
 | 
					        d = "~/appdata/roaming/undetected_chromedriver"
 | 
				
			||||||
 | 
					    elif "LAMBDA_TASK_ROOT" in os.environ:
 | 
				
			||||||
 | 
					        d = "/tmp/undetected_chromedriver"
 | 
				
			||||||
 | 
					    elif platform.startswith(("linux", "linux2")):
 | 
				
			||||||
 | 
					        d = "~/.local/share/undetected_chromedriver"
 | 
				
			||||||
 | 
					    elif platform.endswith("darwin"):
 | 
				
			||||||
 | 
					        d = "~/Library/Application Support/undetected_chromedriver"
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        d = "~/.undetected_chromedriver"
 | 
				
			||||||
 | 
					    data_path = os.path.abspath(os.path.expanduser(d))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        executable_path=None,
 | 
				
			||||||
 | 
					        force=False,
 | 
				
			||||||
 | 
					        version_main: int = 0,
 | 
				
			||||||
 | 
					        user_multi_procs=False,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            executable_path: None = automatic
 | 
				
			||||||
 | 
					                             a full file path to the chromedriver executable
 | 
				
			||||||
 | 
					            force: False
 | 
				
			||||||
 | 
					                    terminate processes which are holding lock
 | 
				
			||||||
 | 
					            version_main: 0 = auto
 | 
				
			||||||
 | 
					                specify main chrome version (rounded, ex: 82)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.force = force
 | 
				
			||||||
 | 
					        self._custom_exe_path = False
 | 
				
			||||||
 | 
					        prefix = secrets.token_hex(8)
 | 
				
			||||||
 | 
					        self.user_multi_procs = user_multi_procs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.is_old_chromedriver = version_main and version_main <= 114
 | 
				
			||||||
 | 
					        # Needs to be called before self.exe_name is accessed
 | 
				
			||||||
 | 
					        self._set_platform_name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not os.path.exists(self.data_path):
 | 
				
			||||||
 | 
					            os.makedirs(self.data_path, exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not executable_path:
 | 
				
			||||||
 | 
					            self.executable_path = os.path.join(
 | 
				
			||||||
 | 
					                self.data_path, "_".join([prefix, self.exe_name])
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not IS_POSIX:
 | 
				
			||||||
 | 
					            if executable_path:
 | 
				
			||||||
 | 
					                if not executable_path[-4:] == ".exe":
 | 
				
			||||||
 | 
					                    executable_path += ".exe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.zip_path = os.path.join(self.data_path, prefix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not executable_path:
 | 
				
			||||||
 | 
					            if not self.user_multi_procs:
 | 
				
			||||||
 | 
					                self.executable_path = os.path.abspath(
 | 
				
			||||||
 | 
					                    os.path.join(".", self.executable_path)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if executable_path:
 | 
				
			||||||
 | 
					            self._custom_exe_path = True
 | 
				
			||||||
 | 
					            self.executable_path = executable_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set the correct repository to download the Chromedriver from
 | 
				
			||||||
 | 
					        if self.is_old_chromedriver:
 | 
				
			||||||
 | 
					            self.url_repo = "https://chromedriver.storage.googleapis.com"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.url_repo = "https://googlechromelabs.github.io/chrome-for-testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.version_main = version_main
 | 
				
			||||||
 | 
					        self.version_full = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_platform_name(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Set the platform and exe name based on the platform undetected_chromedriver is running on
 | 
				
			||||||
 | 
					        in order to download the correct chromedriver.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.platform.endswith("win32"):
 | 
				
			||||||
 | 
					            self.platform_name = "win32"
 | 
				
			||||||
 | 
					            self.exe_name %= ".exe"
 | 
				
			||||||
 | 
					        if self.platform.endswith(("linux", "linux2")):
 | 
				
			||||||
 | 
					            self.platform_name = "linux64"
 | 
				
			||||||
 | 
					            self.exe_name %= ""
 | 
				
			||||||
 | 
					        if self.platform.endswith("darwin"):
 | 
				
			||||||
 | 
					            if self.is_old_chromedriver:
 | 
				
			||||||
 | 
					                self.platform_name = "mac64"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.platform_name = "mac-x64"
 | 
				
			||||||
 | 
					            self.exe_name %= ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def auto(self, executable_path=None, force=False, version_main=None, _=None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            executable_path:
 | 
				
			||||||
 | 
					            force:
 | 
				
			||||||
 | 
					            version_main:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        p = pathlib.Path(self.data_path)
 | 
				
			||||||
 | 
					        if self.user_multi_procs:
 | 
				
			||||||
 | 
					            with Lock():
 | 
				
			||||||
 | 
					                files = list(p.rglob("*chromedriver*"))
 | 
				
			||||||
 | 
					                most_recent = max(files, key=lambda f: f.stat().st_mtime)
 | 
				
			||||||
 | 
					                files.remove(most_recent)
 | 
				
			||||||
 | 
					                list(map(lambda f: f.unlink(), files))
 | 
				
			||||||
 | 
					                if self.is_binary_patched(most_recent):
 | 
				
			||||||
 | 
					                    self.executable_path = str(most_recent)
 | 
				
			||||||
 | 
					                    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if executable_path:
 | 
				
			||||||
 | 
					            self.executable_path = executable_path
 | 
				
			||||||
 | 
					            self._custom_exe_path = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self._custom_exe_path:
 | 
				
			||||||
 | 
					            ispatched = self.is_binary_patched(self.executable_path)
 | 
				
			||||||
 | 
					            if not ispatched:
 | 
				
			||||||
 | 
					                return self.patch_exe()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if version_main:
 | 
				
			||||||
 | 
					            self.version_main = version_main
 | 
				
			||||||
 | 
					        if force is True:
 | 
				
			||||||
 | 
					            self.force = force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.unlink(self.executable_path)
 | 
				
			||||||
 | 
					        except PermissionError:
 | 
				
			||||||
 | 
					            if self.force:
 | 
				
			||||||
 | 
					                self.force_kill_instances(self.executable_path)
 | 
				
			||||||
 | 
					                return self.auto(force=not self.force)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if self.is_binary_patched():
 | 
				
			||||||
 | 
					                    # assumes already running AND patched
 | 
				
			||||||
 | 
					                    return True
 | 
				
			||||||
 | 
					            except PermissionError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					            # return False
 | 
				
			||||||
 | 
					        except FileNotFoundError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        release = self.fetch_release_number()
 | 
				
			||||||
 | 
					        self.version_main = release.version[0]
 | 
				
			||||||
 | 
					        self.version_full = release
 | 
				
			||||||
 | 
					        self.unzip_package(self.fetch_package())
 | 
				
			||||||
 | 
					        return self.patch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def driver_binary_in_use(self, path: str = None) -> bool:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        naive test to check if a found chromedriver binary is
 | 
				
			||||||
 | 
					        currently in use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            path: a string or PathLike object to the binary to check.
 | 
				
			||||||
 | 
					                  if not specified, we check use this object's executable_path
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not path:
 | 
				
			||||||
 | 
					            path = self.executable_path
 | 
				
			||||||
 | 
					        p = pathlib.Path(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not p.exists():
 | 
				
			||||||
 | 
					            raise OSError("file does not exist: %s" % p)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with open(p, mode="a+b") as fs:
 | 
				
			||||||
 | 
					                exc = []
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    fs.seek(0, 0)
 | 
				
			||||||
 | 
					                except PermissionError as e:
 | 
				
			||||||
 | 
					                    exc.append(e)  # since some systems apprently allow seeking
 | 
				
			||||||
 | 
					                    # we conduct another test
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    fs.readline()
 | 
				
			||||||
 | 
					                except PermissionError as e:
 | 
				
			||||||
 | 
					                    exc.append(e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if exc:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return True
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					            # ok safe to assume this is in use
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            # logger.exception("whoops ", e)
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cleanup_unused_files(self):
 | 
				
			||||||
 | 
					        p = pathlib.Path(self.data_path)
 | 
				
			||||||
 | 
					        items = list(p.glob("*undetected*"))
 | 
				
			||||||
 | 
					        for item in items:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                item.unlink()
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def patch(self):
 | 
				
			||||||
 | 
					        self.patch_exe()
 | 
				
			||||||
 | 
					        return self.is_binary_patched()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fetch_release_number(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Gets the latest major version available, or the latest major version of self.target_version if set explicitly.
 | 
				
			||||||
 | 
					        :return: version string
 | 
				
			||||||
 | 
					        :rtype: LooseVersion
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # Endpoint for old versions of Chromedriver (114 and below)
 | 
				
			||||||
 | 
					        if self.is_old_chromedriver:
 | 
				
			||||||
 | 
					            path = f"/latest_release_{self.version_main}"
 | 
				
			||||||
 | 
					            path = path.upper()
 | 
				
			||||||
 | 
					            logger.debug("getting release number from %s" % path)
 | 
				
			||||||
 | 
					            return LooseVersion(urlopen(self.url_repo + path).read().decode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Endpoint for new versions of Chromedriver (115+)
 | 
				
			||||||
 | 
					        if not self.version_main:
 | 
				
			||||||
 | 
					            # Fetch the latest version
 | 
				
			||||||
 | 
					            path = "/last-known-good-versions-with-downloads.json"
 | 
				
			||||||
 | 
					            logger.debug("getting release number from %s" % path)
 | 
				
			||||||
 | 
					            with urlopen(self.url_repo + path) as conn:
 | 
				
			||||||
 | 
					                response = conn.read().decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            last_versions = json.loads(response)
 | 
				
			||||||
 | 
					            return LooseVersion(last_versions["channels"]["Stable"]["version"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fetch the latest minor version of the major version provided
 | 
				
			||||||
 | 
					        path = "/latest-versions-per-milestone-with-downloads.json"
 | 
				
			||||||
 | 
					        logger.debug("getting release number from %s" % path)
 | 
				
			||||||
 | 
					        with urlopen(self.url_repo + path) as conn:
 | 
				
			||||||
 | 
					            response = conn.read().decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        major_versions = json.loads(response)
 | 
				
			||||||
 | 
					        return LooseVersion(major_versions["milestones"][str(self.version_main)]["version"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_exe_version(self):
 | 
				
			||||||
 | 
					        with io.open(self.executable_path, "rb") as f:
 | 
				
			||||||
 | 
					            for line in iter(lambda: f.readline(), b""):
 | 
				
			||||||
 | 
					                match = re.search(rb"platform_handle\x00content\x00([0-9.]*)", line)
 | 
				
			||||||
 | 
					                if match:
 | 
				
			||||||
 | 
					                    return LooseVersion(match[1].decode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fetch_package(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Downloads ChromeDriver from source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: path to downloaded file
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        zip_name = f"chromedriver_{self.platform_name}.zip"
 | 
				
			||||||
 | 
					        if self.is_old_chromedriver:
 | 
				
			||||||
 | 
					            download_url = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, zip_name)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            zip_name = zip_name.replace("_", "-", 1)
 | 
				
			||||||
 | 
					            download_url = "https://storage.googleapis.com/chrome-for-testing-public/%s/%s/%s"
 | 
				
			||||||
 | 
					            download_url %= (self.version_full.vstring, self.platform_name, zip_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.debug("downloading from %s" % download_url)
 | 
				
			||||||
 | 
					        return urlretrieve(download_url)[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unzip_package(self, fp):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Does what it says
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: path to unpacked executable
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        exe_path = self.exe_name
 | 
				
			||||||
 | 
					        if not self.is_old_chromedriver:
 | 
				
			||||||
 | 
					            # The new chromedriver unzips into its own folder
 | 
				
			||||||
 | 
					            zip_name = f"chromedriver-{self.platform_name}"
 | 
				
			||||||
 | 
					            exe_path = os.path.join(zip_name, self.exe_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.debug("unzipping %s" % fp)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.unlink(self.zip_path)
 | 
				
			||||||
 | 
					        except (FileNotFoundError, OSError):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        os.makedirs(self.zip_path, mode=0o755, exist_ok=True)
 | 
				
			||||||
 | 
					        with zipfile.ZipFile(fp, mode="r") as zf:
 | 
				
			||||||
 | 
					            zf.extractall(self.zip_path)
 | 
				
			||||||
 | 
					        os.rename(os.path.join(self.zip_path, exe_path), self.executable_path)
 | 
				
			||||||
 | 
					        os.remove(fp)
 | 
				
			||||||
 | 
					        shutil.rmtree(self.zip_path)
 | 
				
			||||||
 | 
					        os.chmod(self.executable_path, 0o755)
 | 
				
			||||||
 | 
					        return self.executable_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def force_kill_instances(exe_name):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        kills running instances.
 | 
				
			||||||
 | 
					        :param: executable name to kill, may be a path as well
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :return: True on success else False
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        exe_name = os.path.basename(exe_name)
 | 
				
			||||||
 | 
					        if IS_POSIX:
 | 
				
			||||||
 | 
					            r = os.system("kill -f -9 $(pidof %s)" % exe_name)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            r = os.system("taskkill /f /im %s" % exe_name)
 | 
				
			||||||
 | 
					        return not r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def gen_random_cdc():
 | 
				
			||||||
 | 
					        cdc = random.choices(string.ascii_letters, k=27)
 | 
				
			||||||
 | 
					        return "".join(cdc).encode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_binary_patched(self, executable_path=None):
 | 
				
			||||||
 | 
					        executable_path = executable_path or self.executable_path
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with io.open(executable_path, "rb") as fh:
 | 
				
			||||||
 | 
					                return fh.read().find(b"undetected chromedriver") != -1
 | 
				
			||||||
 | 
					        except FileNotFoundError:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def patch_exe(self):
 | 
				
			||||||
 | 
					        start = time.perf_counter()
 | 
				
			||||||
 | 
					        logger.info("patching driver executable %s" % self.executable_path)
 | 
				
			||||||
 | 
					        with io.open(self.executable_path, "r+b") as fh:
 | 
				
			||||||
 | 
					            content = fh.read()
 | 
				
			||||||
 | 
					            # match_injected_codeblock = re.search(rb"{window.*;}", content)
 | 
				
			||||||
 | 
					            match_injected_codeblock = re.search(rb"\{window\.cdc.*?;\}", content)
 | 
				
			||||||
 | 
					            if match_injected_codeblock:
 | 
				
			||||||
 | 
					                target_bytes = match_injected_codeblock[0]
 | 
				
			||||||
 | 
					                new_target_bytes = (
 | 
				
			||||||
 | 
					                    b'{console.log("undetected chromedriver 1337!")}'.ljust(
 | 
				
			||||||
 | 
					                        len(target_bytes), b" "
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                new_content = content.replace(target_bytes, new_target_bytes)
 | 
				
			||||||
 | 
					                if new_content == content:
 | 
				
			||||||
 | 
					                    logger.warning(
 | 
				
			||||||
 | 
					                        "something went wrong patching the driver binary. could not find injection code block"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    logger.debug(
 | 
				
			||||||
 | 
					                        "found block:\n%s\nreplacing with:\n%s"
 | 
				
			||||||
 | 
					                        % (target_bytes, new_target_bytes)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                fh.seek(0)
 | 
				
			||||||
 | 
					                fh.write(new_content)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "patching took us {:.2f} seconds".format(time.perf_counter() - start)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return "{0:s}({1:s})".format(
 | 
				
			||||||
 | 
					            self.__class__.__name__,
 | 
				
			||||||
 | 
					            self.executable_path,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __del__(self):
 | 
				
			||||||
 | 
					        if self._custom_exe_path:
 | 
				
			||||||
 | 
					            # if the driver binary is specified by user
 | 
				
			||||||
 | 
					            # we assume it is important enough to not delete it
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            timeout = 3  # stop trying after this many seconds
 | 
				
			||||||
 | 
					            t = time.monotonic()
 | 
				
			||||||
 | 
					            now = lambda: time.monotonic()
 | 
				
			||||||
 | 
					            while now() - t > timeout:
 | 
				
			||||||
 | 
					                # we don't want to wait until the end of time
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    if self.user_multi_procs:
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                    os.unlink(self.executable_path)
 | 
				
			||||||
 | 
					                    logger.debug("successfully unlinked %s" % self.executable_path)
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                except (OSError, RuntimeError, PermissionError):
 | 
				
			||||||
 | 
					                    time.sleep(0.01)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                except FileNotFoundError:
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
							
								
								
									
										7
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					requests
 | 
				
			||||||
 | 
					setuptools
 | 
				
			||||||
 | 
					python-dotenv
 | 
				
			||||||
 | 
					tls-client
 | 
				
			||||||
 | 
					undetected-chromedriver
 | 
				
			||||||
 | 
					selenium
 | 
				
			||||||
 | 
					grpc_interceptor_headers
 | 
				
			||||||
							
								
								
									
										14
									
								
								runner.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								runner.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TARGET_DIR=$(python -c "import undetected_chromedriver as uc; print(uc.__path__[0])" 2>/dev/null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -d "$TARGET_DIR" ]; then
 | 
				
			||||||
 | 
					    echo "patcher.py в $TARGET_DIR"
 | 
				
			||||||
 | 
					    cp patcher.py "$TARGET_DIR"
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    echo "undetected_chromedriver не найден"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pip install -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					python initiator.py
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user