├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ └── dependency-review.yml ├── .gitignore ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── dev_stuff ├── bet365.com_analysis │ ├── ProductCommon_v1.js │ ├── ProductCommon_v1.js_seed=________________________.js │ ├── ProductCommon_v1.js_seed=________________________.raw.js │ ├── README.md │ └── unknown.js ├── devtools_anti_detector.js ├── multi_thread.py └── nowsecure.nl_analysis │ ├── README.md │ ├── invisible.js │ ├── raw.invisible.js │ ├── raw.v1ray=________________.js │ └── v1ray=________________.js ├── google-colab └── selenium_profiles.ipynb ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── selenium_profiles │ ├── __init__.py │ ├── files │ ├── __init__.py │ └── tmp │ │ └── __init__.py │ ├── js │ ├── __init__.py │ ├── export_profile.js │ ├── fetch.js │ └── undetected │ │ ├── __init.py │ │ ├── get_cdc_props.js │ │ ├── navigator_webdriver.js │ │ └── remove_cdc_props.js │ ├── profiles │ ├── __init__.py │ ├── default.json │ ├── example.json │ ├── profiles.py │ └── scratch.json │ ├── scripts │ ├── __init__.py │ ├── driver_utils.py │ ├── profiles.py │ ├── proxy.py │ └── undetected.py │ ├── utils │ ├── __init__.py │ ├── colab_utils.py │ └── utils.py │ └── webdriver.py └── tests ├── selenium_detector.py └── test_driver.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | ``` 15 | # python-code 16 | ``` 17 | 18 | **Expected behavior or error-message** 19 | A clear and concise description of what you expected to happen. 20 | ``` 21 | # error-message 22 | ``` 23 | 24 | **Environment (please complete the following information):** 25 | - OS: [e.g. Windows, Google-Colab ] 26 | - OS-Version [e.g. 10] 27 | - Browser [e.g. `Google-Chrome, 115.0.5790.166 (Official Build) (64-Bit) `] 28 | - Selenium-Profiles Version [e.g. 2.2.5.1] 29 | - Selenium Version [e.g. 4.11.1] 30 | - selenium-driverless Version [e.g. 1.4.1] 31 | - Python versin [e.g. 3.7.9] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v3 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v2 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /google/ 3 | /scratch/ 4 | /src/selenium_profiles.egg-info/ 5 | /src/selenium_profiles/files/buster/ 6 | /src/selenium_profiles/files/modheader/ 7 | /venv/ 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Shield: [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa] 4 | 5 | This work is licensed under a 6 | [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa]. 7 | 8 | [![CC BY-NC-SA 4.0][cc-by-nc-sa-image]][cc-by-nc-sa] 9 | 10 | [cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/ 11 | [cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png 12 | [cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg 13 | 14 | ## Disclaimer 15 | 16 | I am not responsible what you use the code for!!! Also no warranty! 17 | 18 | ## Author 19 | 20 | [Aurin Aegerter](mailto:aurin.aegerter@stud.gymthun.ch) 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include pyproject.toml 2 | include *.md 3 | include LICENSE.md 4 | recursive-include tests test*.py 5 | graft src/selenium_profiles/js 6 | graft src/selenium_profiles/profiles 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selenium-Profiles 2 | 3 | [![Downloads](https://static.pepy.tech/badge/selenium-profiles)](https://pepy.tech/project/selenium-profiles) [![](https://img.shields.io/pypi/v/selenium-profiles.svg?color=3399EE)](https://pypi.org/project/selenium-profiles/) 4 | 5 | * Overwrite **device metrics** using Selenium 6 | * Mobile and Desktop **emulation** 7 | * **Undetected** by Google, Cloudflare, creep-js using [selenium-driverless](#selenium-driverless) 8 | * [Modifying headers](#Modify-headers) supported using [Selenium-Interceptor](https://github.com/kaliiiiiiiiii/Selenium-Interceptor) or seleniumwire 9 | * [Touch Actions](#Touch_actions) 10 | * dynamic proxies with authentication 11 | * making single [POST](https://github.com/kaliiiiiiiiii/Selenium-Profiles/discussions/11#discussioncomment-4797109), GET or other requests using `driver.profiles.fetch(url)` ([syntax](https://developer.mozilla.org/en-US/docs/Web/API/fetch#syntax)) 12 | * headless unofficially supported 13 | * apply profile on already running driver with `driver.profiles.apply(profiles.Android())` 14 | * use of [seleniumwire](https://github.com/wkeeling/selenium-wire) 15 | 16 | ## Sponsors 17 | This project is currently not being sponsored. 18 | 19 | for the latest features, have a look at the `dev` branch 20 | 21 | ## Getting Started 22 | 23 | ### Dependencies 24 | 25 | * [Python >= 3.7](https://www.python.org/downloads/) 26 | * [Chrome-Browser](https://www.google.de/chrome/) installed 27 | 28 | ### Installing 29 | 30 | * Install [Google-Chrome](https://www.google.de/chrome/) (or another chromium-based browser) 31 | * ```pip install selenium-profiles``` 32 | 33 | ### Start Driver 34 | 35 | ```python 36 | from selenium_profiles.webdriver import Chrome 37 | from selenium_profiles.profiles import profiles 38 | from selenium.webdriver.common.by import By # locate elements 39 | from seleniumwire import webdriver 40 | 41 | 42 | profile = profiles.Windows() # or .Android 43 | options = webdriver.ChromeOptions() 44 | options.add_argument("--headless=new") 45 | driver = Chrome(profile, options=options, 46 | uc_driver=False 47 | ) 48 | 49 | # get url 50 | driver.get('https://abrahamjuliot.github.io/creepjs/') # test fingerprint 51 | 52 | input("Press ENTER to exit: ") 53 | driver.quit() # Execute on the End! 54 | ``` 55 | 56 | Don't forget to execute 57 | ```driver.quit()``` 58 | in the End. Else-wise your temporary folder will get flooded! 59 | 60 | #### Run with Google-Colab 61 | __[Google-Colab](https://colab.research.google.com/github/kaliiiiiiiiii/Selenium-Profiles/blob/master/google-colab/selenium_profiles.ipynb) (file: master@google-colab/selenium_profiles.ipynb)__ 62 | 63 | ## Profiles 64 | 65 | Example Profile: 66 | ```python 67 | profile = \ 68 | { 69 | "options": { 70 | "sandbox": True, 71 | "headless": False, 72 | "load_images": True, 73 | "incognito": True, 74 | "touch": True, 75 | "app": False, 76 | "gpu": False, 77 | "proxy": "http://example-proxy.com:9000", # note: auth not supported, 78 | "extension_paths": ["path/to/extension_1", ...], # directory, .crx or .zip 79 | "args": ["--my-arg1", ...], 80 | "capabilities": {"cap_1":"val_1", "cap_2":"val_2"}, 81 | "experimental_options":{"option1":"value1", "option2":"value2"}, 82 | "adb": False, # run on harware device over ADB 83 | "adb_package": "com.android.chrome", 84 | "use_running_app": True 85 | }, 86 | "cdp": { 87 | "touch": True, 88 | "darkmode":None, 89 | "maxtouchpoints": 5, 90 | "cores":8, 91 | "cdp_args": [], 92 | "emulation": {"mobile":True,"width": 384, "height": 700, "deviceScaleFactor": 10, 93 | "screenOrientation": {"type": "portrait-primary", "angle": 0}}, 94 | "patch_version": True, # to patch automatically, or use "111.0.5563.111" 95 | "useragent": { 96 | "platform": "Linux aarch64", 97 | "acceptLanguage":"en-US", 98 | "userAgent": "Mozilla/5.0 (Linux; Android 11; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", 99 | "userAgentMetadata": { 100 | "brands": [{"brand": "Google Chrome", "version": "105"}, {"brand": "Not)A;Brand", "version": "8"}, 101 | {"brand": "Chromium", "version": "105"}], 102 | "fullVersionList": [{"brand": "Google Chrome", "version": "105.0.5195.136"}, 103 | {"brand": "Not)A;Brand", "version": "8.0.0.0"}, 104 | {"brand": "Chromium", "version": "105.0.5195.136"}], 105 | "fullVersion": "105.0.5195.136", 106 | "platform": "Android", 107 | "platformVersion": "11.0.0", 108 | "architecture": "", 109 | "model": "HD1913", 110 | "mobile": True, 111 | "bitness": "", 112 | "wow64": False} 113 | } 114 | }, 115 | "proxy":{ 116 | "proxy":"socks5://user1:pass@example_jost.com:5001", 117 | "bypass_list":["localhost"] 118 | } 119 | } 120 | ``` 121 | 122 | ### selenium-driverless 123 | warning: 124 | - this package is experimental and might include bugs, please report them at [bug-reports](https://github.com/kaliiiiiiiiii/Selenium-Driverless/issues) 125 | - might only work with 64-bit Python installations 126 | - profiles might make it detected 127 | - requires `pip install selenium-driverless>=1.3.4` first 128 | ```python 129 | from selenium_profiles.webdriver import Chrome 130 | from selenium_profiles.profiles import profiles 131 | from selenium_driverless.webdriver import ChromeOptions 132 | from selenium_driverless.types.by import By 133 | 134 | profile = profiles.Windows() # or .Android 135 | options = ChromeOptions() 136 | # options.add_argument("--headless=new") 137 | driver = Chrome(profile, options=options, driverless_options=True) 138 | 139 | # get url 140 | driver.get('https://nowsecure.nl#relax') # test fingerprint 141 | 142 | driver.quit() # Execute on the End! 143 | ``` 144 | see [documentation](https://github.com/kaliiiiiiiiii/Selenium-Driverless) for usages 145 | 146 | 147 | ### Modify-headers 148 | 149 | using selenium-wire 150 | ```python 151 | from selenium_profiles import webdriver 152 | from selenium_profiles.profiles import profiles 153 | 154 | profile = profiles.Android() 155 | 156 | driver = webdriver.Chrome(profile, uc_driver=False, seleniumwire_options=True) # or pass seleniumwire-options 157 | 158 | def interceptor(request): 159 | request.headers['New-Header'] = 'Some Value' 160 | driver.request_interceptor = interceptor 161 | 162 | # checkout headers 163 | driver.get("https://httpbin.org/headers") 164 | 165 | input("Press ENTER to quit..") 166 | driver.quit() 167 | exit() 168 | ``` 169 | 170 | Using [Selenium-Injector](https://github.com/kaliiiiiiiiii/Selenium-Injector) 171 | ```python 172 | from selenium_profiles.webdriver import Chrome 173 | 174 | driver = Chrome(injector_options=True) 175 | injector = driver.profiles.injector 176 | 177 | # modify headers 178 | injector.declarativeNetRequest.update_headers({"test": "test_2", "sec-ch-ua-platform": "Android"}) 179 | rules = injector.declarativeNetRequest.dynamic_rules 180 | headers = injector.declarativeNetRequest._headers 181 | 182 | driver.get("https://httpbin.org/headers") 183 | input("press ENTER to continue") 184 | 185 | # block images 186 | injector.declarativeNetRequest.update_block_on(resource_types=["image"]) 187 | 188 | driver.get("https://www.wikimedia.org/") 189 | 190 | input("press ENTER to exit") 191 | driver.quit() 192 | ``` 193 | 194 | ### Touch_actions 195 | 196 | Example demonstration script 197 | ```python 198 | from selenium_profiles.webdriver import Chrome 199 | from selenium_profiles.profiles import profiles 200 | from selenium.webdriver.common.by import By 201 | from selenium.webdriver import ChromeOptions 202 | 203 | from selenium_profiles.scripts.driver_utils import TouchActionChain 204 | 205 | 206 | # Start Driver 207 | options = ChromeOptions() 208 | profile = profiles.Android() # or .Windows() 209 | 210 | driver = Chrome(profile, uc_driver=False, options=options) 211 | 212 | # initialise touch_actions 213 | chain = TouchActionChain(driver) 214 | 215 | driver.get("https://cps-check.com/de/multi-touch-test") 216 | 217 | touch_box = driver.find_element(By.XPATH,'//*[@id="box"]') # Get element 218 | 219 | 220 | 221 | chain.touch_and_hold(touch_box) 222 | chain.pause(10) 223 | chain.release(touch_box) 224 | 225 | # perform actions 226 | chain.perform() 227 | 228 | # now you should see a touch indication 229 | # point on the Website for 10 seconds 230 | 231 | # quit driver 232 | input('Press ENTER to quit Driver\n') 233 | driver.quit() 234 | ``` 235 | 236 | ### connect to running driver 237 | Undetectability isn't garanteed 238 | ```python 239 | from selenium import webdriver 240 | driver = webdriver.Chrome() 241 | # driver allready started:) 242 | 243 | 244 | from selenium_profiles.webdriver import profiles as profile_manager 245 | from selenium_profiles.profiles import profiles 246 | 247 | profile = profiles.Android() # or .Android() 248 | driver.profiles = profile_manager(driver=driver, profile=profile) 249 | driver.profiles.apply(profile) 250 | 251 | driver.get('https://hmaker.github.io/selenium-detector/') # test fingerprint 252 | 253 | input("Press ENTER to exit") 254 | driver.quit() # Execute on the End! 255 | ``` 256 | 257 | ### Set proxies dynamically or with options 258 | ```python 259 | from selenium_profiles.webdriver import Chrome 260 | from selenium_profiles.profiles import profiles 261 | 262 | profile = profiles.Windows() # or .Android() 263 | profile["proxy"] = { 264 | "proxy":"http://user1:pass1@example_host.com:41149" 265 | } 266 | 267 | driver = Chrome(profile=profile, injector_options=True) 268 | 269 | driver.profiles.proxy.set_single("http://user2:pass2@example_host.com:41149") 270 | print(driver.profiles.proxy.proxy) 271 | 272 | driver.quit() # Execute on the End! 273 | ``` 274 | 275 | ### To export a profile: 276 | 277 | go to [https://js.do/kaliiiiiiiiiii/get_profile](https://js.do/kaliiiiiiiiiii/get_profile) in your browser and copy the text. 278 | 279 | ## Help 280 | 281 | Please feel free to open an issue or fork! 282 | 283 | ## Known Bugs 284 | 285 | - [click_as_touch makes automation hung](https://github.com/kaliiiiiiiiii/Selenium-Profiles/issues/1) 286 | 287 | 288 | ## Todo 289 | - [x] js-undetectability 290 | - [ ] [`navigator.connection`] 291 | - [ ] fonts don't match platform 292 | - [ ] does not match worker scope (Emulation) [crbug#1358491](https://bugs.chromium.org/p/chromium/issues/detail?id=1358491) 293 | - `Navigator.userAgent` 294 | - `Navigator.platform` 295 | - `navigator.hardwareConcurrency` 296 | - [ ] emulation leak on new tabs [diskussion](https://github.com/kaliiiiiiiiii/Selenium-Profiles/discussions/50) 297 | - [ ] [selenium-detector](https://github.com/HMaker/HMaker.github.io/blob/master/selenium-detector/chromedriver.js) 298 | - [ ] Either Devtools Console is open or CDP Runtime Domain is enabled => patch javascript objects using a Proxy or disable CDP.Runtime domain? 299 | - [ ] [document.$cdc_asdjflasutopfhvcZLmcfl_](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/chromedriver/js/call_function.js;l=219) 300 | - [ ] [`document.$chrome_asyncScriptInfo`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/chromedriver/chrome/web_view_impl.cc;l=1586-1597;drc=2e14a3ac178ee87aa9154e5a15dcd986af1b6059) 301 | - [ ] driver.execute_script() usage (needs hook on called element) 302 | - [ ] driver.execute_async_script() usage (needs hook on called element) 303 | - [ ] driver.find_element() usage 304 | - [x] [`window.cdc_adoQpoasnfa76pfcZLmcfl`](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/chromedriver/chrome/devtools_client_impl.cc;l=526-532;drc=f915006bb8e09e0c29016cf9ab9e737cdebc1adc) 305 | - [x] default metrics 306 | - [x] Android 307 | - [x] Windows 308 | - [ ] IOS 309 | - [ ] Linux 310 | - [ ] Tablet 311 | - [ ] test.py script 312 | - [x] test_driver.py 313 | - [x] assert useragent, profile_export (no error) 314 | - [x] Windows 315 | - [x] useragent-data 316 | - [ ] undetected 317 | - [ ] headless 318 | - [x] Android 319 | - [x] useragent-data 320 | - [ ] undetected 321 | - [ ] headless 322 | 323 | 324 | ## Deprecated 325 | 326 | * [Stealth method]((https://github.com/diprajpatra/selenium-stealth)) (Detected by Google) 327 | * [buster captcha solver](https://github.com/dessant/buster) | [wontfix](https://github.com/kaliiiiiiiiii/Selenium_Profiles/issues/3) 328 | 329 | 330 | ## Authors 331 | 332 | [Aurin Aegerter](mailto:aurinliun@gmx.ch) 333 | 334 | ## License 335 | 336 | Shield: [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa] 337 | 338 | This work is licensed under a 339 | [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa]. 340 | 341 | [![CC BY-NC-SA 4.0][cc-by-nc-sa-image]][cc-by-nc-sa] 342 | 343 | [cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/ 344 | [cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png 345 | [cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg 346 | 347 | ## Disclaimer 348 | 349 | I am not responsible what you use the code for!!! Also no warranty! 350 | 351 | ## Acknowledgments 352 | 353 | Inspiration, code snippets, etc. 354 | 355 | * [Selenium](https://github.com/SeleniumHQ/selenium) 356 | * [selenium-documentation](https://www.selenium.dev/documentation/) 357 | * [README-Template](https://gist.github.com/DomPizzie/7a5ff55ffa9081f2de27c315f5018afc) 358 | * [headless_js](https://github.com/microlinkhq/browserless/tree/master/packages/goto/src/evasions) 359 | * [Selenium-Stealth](https://github.com/diprajpatra/selenium-stealth) 360 | * [Undetected-Chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver) 361 | * [Selenium-Wire](https://github.com/wkeeling/selenium-wire) 362 | * [Modheader-Selenium](https://github.com/modheader/modheader_selenium) 363 | * [ModHeader docs](https://docs.modheader.com/advanced/selenium-webdriver) 364 | * [buster captcha solver](https://github.com/dessant/buster) | [wontfix](https://github.com/kaliiiiiiiiii/Selenium-Profiles/issues/3) 365 | * [audio_captcha_solver](https://github.com/najmi9/solve-recaptcha-python-selenium/blob/master/main.py) 366 | * [Chromedriver-Options List](https://peter.sh/experiments/chromium-command-line-switches/) 367 | * [Chrome DevTools Protocol (cdp_cmd)](https://chromedevtools.github.io/devtools-protocol/1-3/) 368 | * [example_pypi_package](https://github.com/tomchen/example_pypi_package) 369 | * [google-colab installer](https://github.com/ultrafunkamsterdam/undetected-chromedriver/issues/108) 370 | * [scripts/touch_action_chain](https://www.reddit.com/r/Appium/comments/rbx1r2/touchaction_deprecated_please_use_w3c_i_stead/) 371 | * [cdp_event_listeners](https://stackoverflow.com/questions/66227508/selenium-4-0-0-beta-1-how-add-event-listeners-in-cdp) 372 | * [proxy-auth](https://github.com/Smartproxy/Selenium-proxy-authentication) 373 | * [webdriver-manager](https://github.com/SergeyPirogov/webdriver_manager) 374 | * [dynamic subclasses](https://stackoverflow.com/a/9270908/20443541) 375 | -------------------------------------------------------------------------------- /dev_stuff/bet365.com_analysis/ProductCommon_v1.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | (function(a) { 3 | "use strict"; 4 | var b = Function.prototype.call.bind(Function.prototype.toString); 5 | var c = []; 6 | var d = []; 7 | var e = { 8 | toString: function() { 9 | var f = c.lastIndexOf(this); 10 | if (f >= 0) { 11 | return d[f] 12 | } 13 | return b(this) 14 | } 15 | }; 16 | e.toString.prototype = void 0; 17 | c = [e.toString]; 18 | d = [b(Function.prototype.toString)]; 19 | function g(h, i) { 20 | if (typeof i !== "function") { 21 | return 22 | } 23 | try { 24 | var j = e.toString.call(i); 25 | d.push(j); 26 | c.push(h); 27 | if (Function.prototype.toString !== e.toString) { 28 | Function.prototype.toString = e.toString 29 | } 30 | } catch (k) {} 31 | } 32 | var l = "ZT4tcOwEFkVEzfiT1T6u"; 33 | var m = Object.call.bind(Object.bind, Object.call); 34 | var n = m(Object.call); 35 | var o = Array.prototype.push; 36 | var p = Array.prototype.indexOf; 37 | var q = Array.prototype.concat; 38 | var r = Array.prototype.slice; 39 | function s() { 40 | return { 41 | __callbacks: [], 42 | notify: function(t) { 43 | var u; 44 | var v = n(r, this.__callbacks); 45 | var w = v.length; 46 | for (var x = w - 1; x >= 0; --x) { 47 | try { 48 | var y = v[x]; 49 | if (y != null) { 50 | var z = y(t, u); 51 | if (z != null) { 52 | u = z 53 | } 54 | } 55 | } catch (A) {} 56 | } 57 | return u 58 | }, 59 | register: function(B) { 60 | n(o, this.__callbacks, B) 61 | }, 62 | unregister: function(C) { 63 | var D = n(p, this.__callbacks, C); 64 | if (D !== -1) { 65 | this.__callbacks[D] = null 66 | } 67 | }, 68 | __merge: function(E) { 69 | if (E != null) { 70 | this.__callbacks = n(q, this.__callbacks, E.__callbacks) 71 | } 72 | } 73 | } 74 | } 75 | var F = Object.hasOwnProperty; 76 | var G = Object.getPrototypeOf; 77 | var H = Object.getOwnPropertyDescriptor; 78 | var I = Object.getOwnPropertyNames; 79 | var J = Object.defineProperty; 80 | var K = Object.call.bind(Object.bind, Object.call); 81 | var L = K(Object.apply); 82 | var M = K(Object.call); 83 | var N = Object.create; 84 | var O = Function.prototype.bind; 85 | var P = Array.prototype.push; 86 | var Q = Array.prototype.indexOf; 87 | var R = ["arguments", "caller"]; 88 | var S; 89 | if (typeof Reflect !== "undefined" && Reflect != null && typeof Reflect.construct === "function") { 90 | S = Reflect.construct 91 | } else { 92 | S = function(T, U) { 93 | var V = [null]; 94 | L(P, V, U); 95 | var W = L(O, T, V); 96 | return new W 97 | } 98 | } 99 | function X(Y, Z) { 100 | var ba = Y; 101 | while (ba != null) { 102 | var bb = H(ba, Z); 103 | if (bb != null) { 104 | return { 105 | containingObj: ba, 106 | desc: bb 107 | } 108 | } 109 | ba = G(ba) 110 | } 111 | return null 112 | } 113 | var bc = N(null); 114 | function bd(be) { 115 | if (be == null) { 116 | return 117 | } 118 | bc = be 119 | } 120 | function bf(bg, bh) { 121 | var bi = bc[bh]; 122 | if (bi == null) { 123 | return null 124 | } 125 | for (var bj = 0; bj < bi.length; ++bj) { 126 | var bk = bi[bj]; 127 | if (bg === bk.object) { 128 | return bk 129 | } 130 | } 131 | return null 132 | } 133 | function bl(bm, bn) { 134 | var bo = bc[bm]; 135 | if (bo == null) { 136 | bo = []; 137 | bc[bm] = bo 138 | } 139 | M(P, bo, bn) 140 | } 141 | function bp(bq, br) { 142 | var bs = X(bq, br); 143 | if (bs == null) { 144 | return void 0 145 | } 146 | var bt = bs.containingObj 147 | , bu = bs.desc; 148 | var bv = bf(bt, br); 149 | if (bv != null) { 150 | return bv.original 151 | } 152 | var bw = bu.value; 153 | bl(br, { 154 | object: bt, 155 | original: bw 156 | }); 157 | return bw 158 | } 159 | function bx(by, bz) { 160 | var bA = X(by, bz); 161 | if (bA == null) { 162 | return void 0 163 | } 164 | var bB = bA.containingObj 165 | , bC = bA.desc; 166 | var bD = bf(bB, bz); 167 | if (bD != null) { 168 | return bD.original 169 | } 170 | if (M(F, bC, "value")) { 171 | return void 0 172 | } 173 | var bE = N(null); 174 | if (bC.get != null) { 175 | bE.get = bC.get 176 | } 177 | if (bC.set != null) { 178 | bE.set = bC.set 179 | } 180 | bl(bz, { 181 | object: bB, 182 | original: bE 183 | }); 184 | return bE 185 | } 186 | function bF(bG, bH, bI) { 187 | if (bI === void 0) { 188 | bI = false 189 | } 190 | var bJ = X(bG, bH); 191 | if (bJ == null) { 192 | return null 193 | } 194 | var bK = bJ.containingObj 195 | , bL = bJ.desc; 196 | var bM = bL.configurable 197 | , bN = bL.writable; 198 | var bO = bL.value; 199 | if (!M(F, bL, "value")) { 200 | return null 201 | } 202 | var bP = N(null); 203 | bP.value = bO; 204 | if (bM === false && bN === false || typeof bO !== "function") { 205 | return { 206 | originals: bP 207 | } 208 | } 209 | var bQ = bf(bK, bH); 210 | if (bQ != null) { 211 | if (bQ.result != null) { 212 | return bQ.result 213 | } 214 | bO = bQ.original; 215 | bP.value = bO 216 | } 217 | var bR = s(); 218 | var bS = s(); 219 | bL.value = function bT() { 220 | var bU = arguments; 221 | var bV = bR.notify({ 222 | args: bU, 223 | thisObj: this 224 | }); 225 | if (bV) { 226 | if (bV.bypassResult != null) { 227 | if (bV.bypassResult.throw) { 228 | throw bV.bypassResult.value 229 | } 230 | return bV.bypassResult.value 231 | } else if (bV.args != null) { 232 | bU = bV.args 233 | } 234 | } 235 | var bW; 236 | var bX = { 237 | args: arguments, 238 | thisObj: this, 239 | threw: true, 240 | result: null 241 | }; 242 | try { 243 | if (bI && this instanceof bT) { 244 | bW = S(bO, bU) 245 | } else { 246 | bW = L(bO, this, bU) 247 | } 248 | bX = { 249 | args: arguments, 250 | thisObj: this, 251 | threw: false, 252 | result: bW 253 | } 254 | } finally { 255 | var bY = bS.notify(bX); 256 | if (bY && bY.bypassResult != null) { 257 | if (bY.bypassResult.throw) { 258 | throw bY.bypassResult.value 259 | } 260 | return bY.bypassResult.value 261 | } 262 | } 263 | return bW 264 | } 265 | ; 266 | var bZ = bL.value; 267 | g(bZ, bO); 268 | var ca = I(bO); 269 | for (var cb = 0; cb < ca.length; ++cb) { 270 | var cc = ca[cb]; 271 | if (M(Q, R, cc) === -1) { 272 | var cd = H(bZ, cc); 273 | if (cd == null || cd.configurable === true || cd.writable === true) { 274 | var ce = H(bO, cc); 275 | if (ce != null) { 276 | if (cd != null && cd.configurable === false && ce.configurable === true) {} else { 277 | J(bZ, cc, ce) 278 | } 279 | } 280 | } 281 | } 282 | } 283 | try { 284 | if (!M(F, bO, "prototype")) { 285 | bZ.prototype = void 0 286 | } 287 | } catch (cf) {} 288 | J(bK, bH, bL); 289 | var cg = { 290 | onBeforeInvoke: bR, 291 | onAfterInvoke: bS, 292 | originals: bP 293 | }; 294 | bl(bH, { 295 | object: bK, 296 | result: cg, 297 | original: bO 298 | }); 299 | return cg 300 | } 301 | function ch(ci, cj) { 302 | var ck = X(ci, cj); 303 | if (ck == null) { 304 | return null 305 | } 306 | var cl = ck.containingObj 307 | , cm = ck.desc; 308 | var cn = cm.configurable; 309 | var co = cm 310 | , cp = co.get 311 | , cq = co.set; 312 | var cr = M(F, cm, "value"); 313 | var cs = N(null); 314 | if (cn === false || cr) { 315 | if (cp != null) { 316 | cs.get = cp 317 | } 318 | if (cq != null) { 319 | cs.set = cq 320 | } 321 | return { 322 | originals: cs 323 | } 324 | } 325 | var ct = bf(cl, cj); 326 | if (ct != null) { 327 | if (ct.result != null) { 328 | return ct.result 329 | } 330 | cp = ct.original.get; 331 | cq = ct.original.set 332 | } 333 | var cu = { 334 | onAfterGet: void 0, 335 | onBeforeGet: void 0, 336 | onAfterSet: void 0, 337 | onBeforeSet: void 0, 338 | originals: {} 339 | }; 340 | if (cp != null) { 341 | cs.get = cp; 342 | var cv = s(); 343 | var cw = s(); 344 | cm.get = function() { 345 | var cx = cv.notify({ 346 | thisObj: this 347 | }); 348 | if (cx && cx.bypassResult != null) { 349 | if (cx.bypassResult.throw) { 350 | throw cx.bypassResult.value 351 | } 352 | return cx.bypassResult.value 353 | } 354 | var cy; 355 | var cz = { 356 | thisObj: this, 357 | result: null, 358 | threw: true 359 | }; 360 | try { 361 | cy = M(cp, this); 362 | cz = { 363 | thisObj: this, 364 | result: cy, 365 | threw: false 366 | } 367 | } finally { 368 | var cA = cw.notify(cz); 369 | if (cA && cA.bypassResult != null) { 370 | if (cA.bypassResult.throw) { 371 | throw cA.bypassResult.value 372 | } 373 | return cA.bypassResult.value 374 | } 375 | } 376 | return cy 377 | } 378 | ; 379 | g(cm.get, cp); 380 | try { 381 | if (!M(F, cp, "prototype")) { 382 | cm.get.prototype = void 0 383 | } 384 | } catch (cB) {} 385 | cu.onBeforeGet = cv; 386 | cu.onAfterGet = cw 387 | } 388 | if (cq != null) { 389 | cs.set = cq; 390 | var cC = s(); 391 | var cD = s(); 392 | cm.set = function(cE) { 393 | var cF = cE; 394 | var cG = cC.notify({ 395 | param: cE, 396 | thisObj: this 397 | }); 398 | if (cG) { 399 | if (cG.bypassResult != null) { 400 | if (cG.bypassResult.throw) { 401 | throw cG.bypassResult.value 402 | } 403 | return cG.bypassResult.value 404 | } else if (M(F, cG, "param")) { 405 | cF = cG.param 406 | } 407 | } 408 | var cH; 409 | var cI = { 410 | param: cE, 411 | thisObj: this, 412 | result: null, 413 | threw: true 414 | }; 415 | try { 416 | cH = M(cq, this, cF); 417 | cI = { 418 | param: cE, 419 | thisObj: this, 420 | result: cH, 421 | threw: false 422 | } 423 | } finally { 424 | var cJ = cD.notify(cI); 425 | if (cJ && cJ.bypassResult != null) { 426 | if (cJ.bypassResult.throw) { 427 | throw cJ.bypassResult.value 428 | } 429 | return cJ.bypassResult.value 430 | } 431 | } 432 | return cH 433 | } 434 | ; 435 | g(cm.set, cq); 436 | try { 437 | if (!M(F, cq, "prototype")) { 438 | cm.set.prototype = void 0 439 | } 440 | } catch (cK) {} 441 | cu.onBeforeSet = cC; 442 | cu.onAfterSet = cD 443 | } 444 | J(cl, cj, cm); 445 | cu.originals = cs; 446 | bl(cj, { 447 | object: cl, 448 | result: cu, 449 | original: cs 450 | }); 451 | return cu 452 | } 453 | try { 454 | if (typeof document.createEvent === "function") { 455 | var cL = document.createEvent("CustomEvent"); 456 | var cM = false; 457 | cL.initCustomEvent(l, false, false, { 458 | exchange: function(cN) { 459 | bd(cN); 460 | cM = true 461 | } 462 | }); 463 | dispatchEvent(cL); 464 | if (!cM) { 465 | var cO = bx(CustomEvent.prototype, "detail"); 466 | if (cO != null && typeof cO.get === "function") { 467 | addEventListener(l, function(cP) { 468 | var cQ = cP; 469 | var cR = M(cO.get, cQ); 470 | if (cR != null && typeof cR.exchange === "function") { 471 | cR.exchange(bc) 472 | } 473 | }) 474 | } 475 | } 476 | } 477 | } catch (cS) {} 478 | var cT = Object.create(null); 479 | var cU = Object.create(null); 480 | { 481 | var cV = "\u202EKDQZPCCsH\u202D"; 482 | var cW = XMLHttpRequest; 483 | var cX; 484 | var cY 485 | } 486 | { 487 | var cZ = window.fetch; 488 | var da 489 | } 490 | var db = "\u202EduIZWEoPX\u202D"; 491 | var dc = "-2\u202EduIZWEoPX\u202D"; 492 | var dd = void 0; 493 | var de = Object.defineProperty.bind(Object); 494 | function df(dg, dh, di, dj, dk) { 495 | if (dj === "function") { 496 | cT[dg] = bF(dl(di), dh, !!dk) 497 | } else if (dj === "accessor") { 498 | cT[dg] = ch(dl(di), dh) 499 | } else if (dj === "originalAccessor") { 500 | cU[dg] = bx(dl(di), dh) 501 | } else if (dj === "originalFunction") { 502 | cU[dg] = bp(dl(di), dh) 503 | } 504 | } 505 | function dl(dm) { 506 | var dn = window; 507 | for (var dp = 0; dp < dm.length; dp++) { 508 | if (!{}.hasOwnProperty.call(dn, dm[dp])) { 509 | return void 0 510 | } 511 | dn = dn[dm[dp]] 512 | } 513 | return dn 514 | } 515 | df("CustomEvent", "CustomEvent", [], "function", true); 516 | df("cancelBubble", "cancelBubble", ["Event", "prototype"], "accessor"); 517 | df("fetch", "fetch", [], "function"); 518 | df("formSubmit", "submit", ["HTMLFormElement", "prototype"], "function"); 519 | df("preventDefault", "preventDefault", ["Event", "prototype"], "function"); 520 | df("setRequestHeader", "setRequestHeader", ["XMLHttpRequest", "prototype"], "originalFunction"); 521 | df("stopImmediatePropagation", "stopImmediatePropagation", ["Event", "prototype"], "function"); 522 | df("stopPropagation", "stopPropagation", ["Event", "prototype"], "function"); 523 | df("xhrOpen", "open", ["XMLHttpRequest", "prototype"], "function"); 524 | df("xhrSend", "send", ["XMLHttpRequest", "prototype"], "function"); 525 | (function() { 526 | if (cW == null) { 527 | return 528 | } 529 | var dq = cU.setRequestHeader != null ? cU.setRequestHeader : cT.setRequestHeader != null ? cT.setRequestHeader.originals.value : null; 530 | if (dq == null) { 531 | return 532 | } 533 | if (cT.xhrOpen != null) { 534 | cX = function(dr, ds) { 535 | if (ds != null && ds.bypassResult != null || dr.args == null || dr.args.length < 2) { 536 | return ds 537 | } 538 | var dt, du = null; 539 | var dv; 540 | var dw = ds != null && ds.args != null ? ds.args : dr.args; 541 | var dt = dw[0]; 542 | var du = dw[1]; 543 | var dv = dw[2] == null ? true : dw[2]; 544 | de(dr.thisObj, cV, { 545 | writable: true, 546 | configurable: true, 547 | enumerable: false, 548 | value: { 549 | method: dt, 550 | url: du 551 | } 552 | }); 553 | return { 554 | args: [dt, du, dv] 555 | } 556 | } 557 | ; 558 | cT.xhrOpen.onBeforeInvoke.register(cX) 559 | } 560 | if (cT.xhrSend != null) { 561 | cY = function(dx, dy) { 562 | if (dy != null && dy.bypassResult != null) { 563 | return dy 564 | } 565 | if (dd != null && cV in dx.thisObj && dd.shouldHook(dx.thisObj[cV])) { 566 | var dz = dd.getEncodedData(); 567 | if (dz) { 568 | for (var dA in dz) { 569 | if (!{}.hasOwnProperty.call(dz, dA)) 570 | continue; 571 | var dB = dz[dA]; 572 | var dC = dd.config.headerNamePrefix + dA; 573 | var dD = dd.chunk(dC, dB, dd.config.headerChunkSize); 574 | for (var dE in dD) { 575 | if (!{}.hasOwnProperty.call(dD, dE)) 576 | continue; 577 | dq.call(dx.thisObj, dE, dD[dE]) 578 | } 579 | } 580 | } 581 | } 582 | return dy 583 | } 584 | ; 585 | cT.xhrSend.onBeforeInvoke.register(cY) 586 | } 587 | }()); 588 | (function() { 589 | if (cZ == null) { 590 | return 591 | } 592 | var dF = window.Request; 593 | if (cT.fetch != null && window.fetch != null && dF != null) { 594 | var dG = function(dH, dI) { 595 | var dJ = dH.args; 596 | if (dI != null) { 597 | if (dI.bypassResult != null) { 598 | return dI 599 | } 600 | if (dI.args != null) { 601 | dJ = dI.args 602 | } 603 | } 604 | if (dJ != null && dJ.length > 0) { 605 | var dK = dJ[0]; 606 | var dL = dJ[1]; 607 | var dM = new dF(dK,dL); 608 | var dN = { 609 | url: dM.url, 610 | method: dM.method 611 | }; 612 | if (dd != null && dd.shouldHook(dN)) { 613 | var dO = dd.getEncodedData(); 614 | if (dO) { 615 | for (var dP in dO) { 616 | if (!{}.hasOwnProperty.call(dO, dP)) 617 | continue; 618 | var dQ = dO[dP]; 619 | var dR = dd.config.headerNamePrefix + dP; 620 | var dS = dd.chunk(dR, dQ, dd.config.headerChunkSize); 621 | for (var dT in dS) { 622 | if (!{}.hasOwnProperty.call(dS, dT)) 623 | continue; 624 | dM.headers.set(dT, dS[dT]) 625 | } 626 | } 627 | } 628 | } 629 | return { 630 | args: [dM] 631 | } 632 | } 633 | return dI 634 | }; 635 | cT.fetch.onBeforeInvoke.register(dG) 636 | } 637 | }()); 638 | addEventListener(db, function dU(dV) { 639 | dd = dV.detail; 640 | removeEventListener(db, dU, true) 641 | }, true); 642 | addEventListener(dc, function dW(dX) { 643 | if (dX.detail != null && dX.detail.exchange != null) { 644 | { 645 | if (cT.xhrOpen != null && cX != null) { 646 | cT.xhrOpen.onBeforeInvoke.unregister(cX) 647 | } 648 | if (cT.xhrSend != null && cY != null) { 649 | cT.xhrSend.onBeforeInvoke.unregister(cY) 650 | } 651 | } 652 | { 653 | if (cT.fetch != null) { 654 | cT.fetch.onBeforeInvoke.unregister(da) 655 | } 656 | } 657 | dX.detail.exchange({ 658 | instrumented: cT 659 | }) 660 | } 661 | removeEventListener(dc, dW, true) 662 | }, true) 663 | }(this)) 664 | }()) 665 | -------------------------------------------------------------------------------- /dev_stuff/bet365.com_analysis/ProductCommon_v1.js_seed=________________________.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Profiles/90d6d4e668c506e1e2d90bdfafff45d9df71e735/dev_stuff/bet365.com_analysis/ProductCommon_v1.js_seed=________________________.js -------------------------------------------------------------------------------- /dev_stuff/bet365.com_analysis/README.md: -------------------------------------------------------------------------------- 1 | # bet365.com analysis 2 | 3 | ### `ProductCommon_v1.js` 4 | - at [365365824.com/members/services/host/Scripts/js/ProductCommon_v1.js](https://www.365365824.com/members/services/host/Scripts/js/ProductCommon_v1.js) 5 | 6 | #### `Object.toString()` patch 7 | ```js 8 | function() { 9 | var f = c.lastIndexOf(this); 10 | if (f >= 0) { 11 | return d[f] 12 | } 13 | return b(this) 14 | } 15 | ``` 16 | where C is a list of functions to patch: 17 | ```json 18 | [ 19 | "function toString() { [native code] }", 20 | "function CustomEvent() { [native code] }", 21 | "function get cancelBubble() { [native code] }", 22 | "function set cancelBubble() { [native code] }", 23 | "function fetch() { [native code] }", 24 | "function submit() { [native code] }", 25 | "function preventDefault() { [native code] }", 26 | "function stopImmediatePropagation() { [native code] }", 27 | "function stopPropagation() { [native code] }", 28 | "function open() { [native code] }", 29 | "function send() { [native code] }" 30 | ] 31 | ``` 32 | 33 | ### `ProductCommon_v1.js?seed=****************` de-obfuscated 34 | - look like obfuscated with [Jscrambler](https://en.wikipedia.org/wiki/Jscrambler) 35 | 36 | ### `unknown.js` 37 | - no idea where it came from (`VM171`) -------------------------------------------------------------------------------- /dev_stuff/devtools_anti_detector.js: -------------------------------------------------------------------------------- 1 | patcher = {} 2 | patcher.proxies = new Set() 3 | patcher.window_keys = ['debug','dir', 'table', 'clear', 'profile', 'profileEnd'] 4 | 5 | patcher.console_keys = ["clear",'debug', 'error', 'info', 'log', 'warn', 'dir', 'dirxml', 'table', 'trace', 'group', 'groupCollapsed', 'groupEnd', 'count', 'countReset', 'assert', 'profile', 'profileEnd', 'time', 'timeLog', 'timeEnd', 'timeStamp', 'context', 'createTask'] 6 | 7 | patcher.sync2async = async function exec(func, args){func(...args)} 8 | 9 | patcher.console_handler = { 10 | get(target, prop, receiver){ 11 | if (patcher.console_keys.includes(prop)){ 12 | var func = Reflect.get(...arguments); 13 | var proxied_func = new Proxy(func, { 14 | apply: function (target, thisArg, args){ 15 | if (patcher.async){patcher.sync2async(func, args)} 16 | return 17 | } 18 | }); 19 | patcher.proxies.add(proxied_func) 20 | return proxied_func 21 | } 22 | return Reflect.get(target, prop, arguments); 23 | } 24 | } 25 | 26 | // window.catch = function(...args){console.log(args), throw(...args)} 27 | 28 | patcher.toStringProxy = new Proxy(Function.prototype.toString, { 29 | apply: function (target, thisArg, args) { 30 | if (patcher.proxies.has(thisArg)) { 31 | return 'function '+thisArg.name+'() { [native code] }'; 32 | } 33 | try{return target.call(thisArg)} 34 | catch(e){e = new Proxy(e, patcher.TypeError_handler); throw e} 35 | } 36 | }); 37 | 38 | patcher.TypeError_handler = {get(target, prop, receiver){ 39 | if (prop == "stack"){ 40 | stack = Reflect.get(target, prop, arguments).split("\n") 41 | stack.splice(-2, 1); 42 | return stack.join("\n") 43 | } 44 | return Reflect.get(target, prop, arguments); 45 | } 46 | } 47 | 48 | patcher.patch_window = function(){ 49 | patcher.window_keys.forEach(function(key, value){ 50 | window[key] = console[key]; 51 | patcher.proxies.add(window[key]) 52 | }); 53 | } 54 | 55 | 56 | patcher.patch = function(async=false){ 57 | patcher.patch_window() 58 | patcher.async = async; 59 | window.console = new Proxy(window.console, patcher.console_handler); 60 | Function.prototype.toString = patcher.toStringProxy; 61 | patcher.proxies.add(Function.prototype.toString); 62 | patcher.patch_window() 63 | } 64 | try{console.log.toString.call(4)}catch(e){before = e} 65 | patcher.patch(true) 66 | try{console.log.toString.call(4)}catch(e){after = e} -------------------------------------------------------------------------------- /dev_stuff/multi_thread.py: -------------------------------------------------------------------------------- 1 | import threading # for exec_timeout 2 | import time 3 | import ctypes # for terminate_thread 4 | import warnings 5 | 6 | 7 | def terminate_thread(thread): 8 | """Terminates a python thread from another thread. 9 | 10 | param thread: a threading.Thread instance 11 | """ 12 | if not thread.is_alive(): 13 | return 14 | 15 | exc = ctypes.py_object(SystemExit) 16 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc( 17 | ctypes.c_long(thread.ident), exc) 18 | if res == 0: 19 | raise ValueError("nonexistent thread id") 20 | elif res > 1: 21 | # """if it returns a number greater than one, you're in trouble, 22 | # and you should call it again with exc=NULL to revert the effect""" 23 | ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None) 24 | raise SystemError("PyThreadState_SetAsyncExc failed") 25 | 26 | 27 | def exec_timeout(target, args=None, timeout=5): 28 | if args is not None: 29 | thread = threading.Thread(target=target, args=args) # args = (arg1,arg2,) | comma in end important! 30 | else: 31 | thread = threading.Thread(target=target) 32 | thread.start() 33 | print('going on') 34 | time.sleep(timeout) 35 | if thread.is_alive(): 36 | terminate_thread(thread) 37 | timeout = str(timeout) 38 | warnings.warn('thread timeout! [' + timeout + ']') 39 | else: 40 | thread.join() 41 | -------------------------------------------------------------------------------- /dev_stuff/nowsecure.nl_analysis/README.md: -------------------------------------------------------------------------------- 1 | # Nowsecure Cloudfare analysis 2 | as 27.6.2023 3 | - same scripts on [bscscan.com/contractsVerified](https://bscscan.com/contractsVerified) 4 | - => not just nowsecure-specific 5 | 6 | ## Service-worker scripts 7 | overwriting javascript and emulation doesn't work there :( 8 | 9 | 10 | #### expose emulation and `navigator.webdriver` 11 | ```js 12 | (function() { 13 | var workerData = { 14 | p: navigator.platform, 15 | l: navigator.languages, 16 | hwC: navigator.hardwareConcurrency, 17 | dM: navigator.deviceMemory, 18 | wd: navigator.webdriver, 19 | uA: navigator.userAgent 20 | }; 21 | postMessage(workerData); 22 | } 23 | )() 24 | ``` 25 | 26 | #### maximum Stack-size 27 | ```js 28 | onmessage = function(e) {var gsb = function(){ 29 | var sizeA = 0; 30 | var sizeB = 0; 31 | var counter = 0; 32 | try { 33 | var fn_1 = function () { 34 | counter += 1; 35 | fn_1(); 36 | }; 37 | fn_1(); 38 | } 39 | catch (_a) { 40 | sizeA = counter; 41 | try { 42 | counter = 0; 43 | var fn_2 = function () { 44 | var local = 1; 45 | counter += local; 46 | fn_2(); 47 | }; 48 | fn_2(); 49 | } 50 | catch (_b) { 51 | sizeB = counter; 52 | } 53 | } 54 | var bytes = (sizeB * 8) / (sizeA - sizeB); 55 | return [sizeA, sizeB, bytes]; 56 | }; postMessage({a3: gsb()})}; 57 | ``` 58 | - seems to get the max stack size: 59 | - with local var 60 | - without local var 61 | - difference in bytes ?? 62 | - might be bypassable with https://stackoverflow.com/a/49601237/20443541 63 | 64 | #### get "speed" of execution within worker 65 | ```js 66 | function getTimingResolution() { 67 | var runs = 5000; 68 | var valA = 1; 69 | var valB = 1; 70 | var res; 71 | for (var i = 0; i < runs; i++) { 72 | var a = performance.now(); 73 | var b = performance.now(); 74 | if (a < b) { 75 | res = b - a; 76 | if (res > valA && res < valB) { 77 | valB = res; 78 | } else if (res < valA) { 79 | valB = valA; 80 | valA = res; 81 | } 82 | } 83 | } 84 | return valA; 85 | }; 86 | 87 | onmessage = function(){ postMessage({ a: getTimingResolution() })} 88 | ``` 89 | 90 | #### detect open devtools with `debugger` statement 91 | ```js 92 | self.onmessage = function(m) { 93 | self.postMessage({ 94 | o: true 95 | }); 96 | eval("debugger"); 97 | self.postMessage({ 98 | o: false 99 | }) 100 | } 101 | ``` 102 | 103 | ## Scripts 104 | look like obfuscated with [Jscrambler](https://en.wikipedia.org/wiki/Jscrambler) 105 | 106 | ### `invisible.js` de-obfuscated 107 | - at [nowsecure.nl/cdn-cgi/challenge-platform/scripts/invisible.js](https://nowsecure.nl/cdn-cgi/challenge-platform/scripts/invisible.js) 108 | - and [bscscan.com/cdn-cgi/challenge-platform/h/b/scripts/jsd/556d0c9f/invisible.js](https://bscscan.com/cdn-cgi/challenge-platform/h/b/scripts/jsd/556d0c9f/invisible.js) 109 | 110 | ### `v1?ray=****************.js` de-obfuscated 111 | 112 | ```js 113 | // assumption: check function 114 | // args[0]: native function to check 115 | // args[1]: globalThis 116 | function u(c, e) { 117 | e instanceof c.Function && 0 < c.Function.prototype.toString.call(e).indexOf('[native code]') 118 | } 119 | ``` 120 | - check `.toString() == '[native code]'` 121 | - check `instanceof Function` 122 | 123 | ```js 124 | // get all keys of object => return [key1, key2, ...] 125 | function w(c) { 126 | for (b = b, 127 | e = []; null !== c; e = e.concat(Object.keys(c)), 128 | c = Object.getPrototypeOf(c)); 129 | return e 130 | } 131 | ``` 132 | check .getPrototypeOf() correlates with .keys() 133 | -------------------------------------------------------------------------------- /dev_stuff/nowsecure.nl_analysis/raw.v1ray=________________.js: -------------------------------------------------------------------------------- 1 | window._cf_chl_opt = { 2 | cFPWv: 'g' 3 | }; 4 | ~function(S, g, h, i, o, s) { 5 | S = b, 6 | function(c, e, R, f, z) { 7 | for (R = b, 8 | f = c(); !![]; ) 9 | try { 10 | if (z = -parseInt(R(395)) / 1 + parseInt(R(366)) / 2 + -parseInt(R(447)) / 3 * (parseInt(R(425)) / 4) + -parseInt(R(407)) / 5 * (-parseInt(R(410)) / 6) + -parseInt(R(374)) / 7 + parseInt(R(381)) / 8 * (-parseInt(R(388)) / 9) + -parseInt(R(383)) / 10 * (-parseInt(R(364)) / 11), 11 | z === e) 12 | break; 13 | else 14 | f.push(f.shift()) 15 | } catch (A) { 16 | f.push(f.shift()) 17 | } 18 | }(a, 855701), 19 | g = this || self, 20 | h = g[S(385)], 21 | i = function(T, e, f, z) { 22 | return T = S, 23 | e = String[T(386)], 24 | f = { 25 | 'h': function(A) { 26 | return A == null ? '' : f.g(A, 6, function(B, U) { 27 | return U = b, 28 | U(389)[U(436)](B) 29 | }) 30 | }, 31 | 'g': function(A, B, C, V, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) { 32 | if (V = T, 33 | null == A) 34 | return ''; 35 | for (E = {}, 36 | F = {}, 37 | G = '', 38 | H = 2, 39 | I = 3, 40 | J = 2, 41 | K = [], 42 | L = 0, 43 | M = 0, 44 | N = 0; N < A[V(438)]; N += 1) 45 | if (O = A[V(436)](N), 46 | Object[V(400)][V(454)][V(445)](E, O) || (E[O] = I++, 47 | F[O] = !0), 48 | P = G + O, 49 | Object[V(400)][V(454)][V(445)](E, P)) 50 | G = P; 51 | else { 52 | if (Object[V(400)][V(454)][V(445)](F, G)) { 53 | if (256 > G[V(457)](0)) { 54 | for (D = 0; D < J; L <<= 1, 55 | M == B - 1 ? (M = 0, 56 | K[V(393)](C(L)), 57 | L = 0) : M++, 58 | D++) 59 | ; 60 | for (Q = G[V(457)](0), 61 | D = 0; 8 > D; L = Q & 1 | L << 1, 62 | M == B - 1 ? (M = 0, 63 | K[V(393)](C(L)), 64 | L = 0) : M++, 65 | Q >>= 1, 66 | D++) 67 | ; 68 | } else { 69 | for (Q = 1, 70 | D = 0; D < J; L = L << 1 | Q, 71 | B - 1 == M ? (M = 0, 72 | K[V(393)](C(L)), 73 | L = 0) : M++, 74 | Q = 0, 75 | D++) 76 | ; 77 | for (Q = G.charCodeAt(0), 78 | D = 0; 16 > D; L = L << 1 | Q & 1.22, 79 | B - 1 == M ? (M = 0, 80 | K[V(393)](C(L)), 81 | L = 0) : M++, 82 | Q >>= 1, 83 | D++) 84 | ; 85 | } 86 | H--, 87 | 0 == H && (H = Math.pow(2, J), 88 | J++), 89 | delete F[G] 90 | } else 91 | for (Q = E[G], 92 | D = 0; D < J; L = 1.06 & Q | L << 1, 93 | B - 1 == M ? (M = 0, 94 | K[V(393)](C(L)), 95 | L = 0) : M++, 96 | Q >>= 1, 97 | D++) 98 | ; 99 | G = (H--, 100 | H == 0 && (H = Math[V(439)](2, J), 101 | J++), 102 | E[P] = I++, 103 | String(O)) 104 | } 105 | if (G !== '') { 106 | if (Object[V(400)][V(454)][V(445)](F, G)) { 107 | if (256 > G[V(457)](0)) { 108 | for (D = 0; D < J; L <<= 1, 109 | B - 1 == M ? (M = 0, 110 | K[V(393)](C(L)), 111 | L = 0) : M++, 112 | D++) 113 | ; 114 | for (Q = G[V(457)](0), 115 | D = 0; 8 > D; L = L << 1 | Q & 1, 116 | B - 1 == M ? (M = 0, 117 | K[V(393)](C(L)), 118 | L = 0) : M++, 119 | Q >>= 1, 120 | D++) 121 | ; 122 | } else { 123 | for (Q = 1, 124 | D = 0; D < J; L = L << 1 | Q, 125 | M == B - 1 ? (M = 0, 126 | K.push(C(L)), 127 | L = 0) : M++, 128 | Q = 0, 129 | D++) 130 | ; 131 | for (Q = G[V(457)](0), 132 | D = 0; 16 > D; L = L << 1 | Q & 1.84, 133 | M == B - 1 ? (M = 0, 134 | K[V(393)](C(L)), 135 | L = 0) : M++, 136 | Q >>= 1, 137 | D++) 138 | ; 139 | } 140 | H--, 141 | H == 0 && (H = Math.pow(2, J), 142 | J++), 143 | delete F[G] 144 | } else 145 | for (Q = E[G], 146 | D = 0; D < J; L = L << 1 | 1.45 & Q, 147 | B - 1 == M ? (M = 0, 148 | K[V(393)](C(L)), 149 | L = 0) : M++, 150 | Q >>= 1, 151 | D++) 152 | ; 153 | H--, 154 | H == 0 && J++ 155 | } 156 | for (Q = 2, 157 | D = 0; D < J; L = 1 & Q | L << 1.3, 158 | M == B - 1 ? (M = 0, 159 | K[V(393)](C(L)), 160 | L = 0) : M++, 161 | Q >>= 1, 162 | D++) 163 | ; 164 | for (; ; ) 165 | if (L <<= 1, 166 | B - 1 == M) { 167 | K[V(393)](C(L)); 168 | break 169 | } else 170 | M++; 171 | return K[V(405)]('') 172 | }, 173 | 'j': function(A, W) { 174 | return W = T, 175 | A == null ? '' : '' == A ? null : f.i(A[W(438)], 32768, function(B, X) { 176 | return X = W, 177 | A[X(457)](B) 178 | }) 179 | }, 180 | 'i': function(A, B, C, Y, D, E, F, G, H, I, J, K, L, M, N, O, Q, P) { 181 | for (Y = T, 182 | D = [], 183 | E = 4, 184 | F = 4, 185 | G = 3, 186 | H = [], 187 | K = C(0), 188 | L = B, 189 | M = 1, 190 | I = 0; 3 > I; D[I] = I, 191 | I += 1) 192 | ; 193 | for (N = 0, 194 | O = Math[Y(439)](2, 2), 195 | J = 1; J != O; P = L & K, 196 | L >>= 1, 197 | 0 == L && (L = B, 198 | K = C(M++)), 199 | N |= (0 < P ? 1 : 0) * J, 200 | J <<= 1) 201 | ; 202 | switch (N) { 203 | case 0: 204 | for (N = 0, 205 | O = Math[Y(439)](2, 8), 206 | J = 1; O != J; P = K & L, 207 | L >>= 1, 208 | 0 == L && (L = B, 209 | K = C(M++)), 210 | N |= J * (0 < P ? 1 : 0), 211 | J <<= 1) 212 | ; 213 | Q = e(N); 214 | break; 215 | case 1: 216 | for (N = 0, 217 | O = Math[Y(439)](2, 16), 218 | J = 1; J != O; P = K & L, 219 | L >>= 1, 220 | L == 0 && (L = B, 221 | K = C(M++)), 222 | N |= (0 < P ? 1 : 0) * J, 223 | J <<= 1) 224 | ; 225 | Q = e(N); 226 | break; 227 | case 2: 228 | return '' 229 | } 230 | for (I = D[3] = Q, 231 | H[Y(393)](Q); ; ) { 232 | if (M > A) 233 | return ''; 234 | for (N = 0, 235 | O = Math[Y(439)](2, G), 236 | J = 1; O != J; P = K & L, 237 | L >>= 1, 238 | L == 0 && (L = B, 239 | K = C(M++)), 240 | N |= J * (0 < P ? 1 : 0), 241 | J <<= 1) 242 | ; 243 | switch (Q = N) { 244 | case 0: 245 | for (N = 0, 246 | O = Math[Y(439)](2, 8), 247 | J = 1; O != J; P = L & K, 248 | L >>= 1, 249 | 0 == L && (L = B, 250 | K = C(M++)), 251 | N |= (0 < P ? 1 : 0) * J, 252 | J <<= 1) 253 | ; 254 | D[F++] = e(N), 255 | Q = F - 1, 256 | E--; 257 | break; 258 | case 1: 259 | for (N = 0, 260 | O = Math.pow(2, 16), 261 | J = 1; J != O; P = L & K, 262 | L >>= 1, 263 | L == 0 && (L = B, 264 | K = C(M++)), 265 | N |= J * (0 < P ? 1 : 0), 266 | J <<= 1) 267 | ; 268 | D[F++] = e(N), 269 | Q = F - 1, 270 | E--; 271 | break; 272 | case 2: 273 | return H[Y(405)]('') 274 | } 275 | if (0 == E && (E = Math.pow(2, G), 276 | G++), 277 | D[Q]) 278 | Q = D[Q]; 279 | else if (F === Q) 280 | Q = I + I[Y(436)](0); 281 | else 282 | return null; 283 | H[Y(393)](Q), 284 | D[F++] = I + Q[Y(436)](0), 285 | E--, 286 | I = Q, 287 | 0 == E && (E = Math[Y(439)](2, G), 288 | G++) 289 | } 290 | } 291 | }, 292 | z = {}, 293 | z[T(384)] = f.h, 294 | z 295 | }(), 296 | o = {}, 297 | o[S(434)] = 'o', 298 | o[S(387)] = 's', 299 | o[S(432)] = 'u', 300 | o[S(373)] = 'z', 301 | o[S(452)] = 'n', 302 | o[S(417)] = 'I', 303 | o[S(446)] = 'b', 304 | s = o, 305 | g[S(399)] = function(f, z, A, B, a7, D, E, F, G, H, I) { 306 | if (a7 = S, 307 | z === null || void 0 === z) 308 | return B; 309 | for (D = w(z), 310 | f.Object[a7(451)] && (D = D[a7(394)](f[a7(379)][a7(451)](z))), 311 | D = f[a7(413)][a7(437)] && f[a7(422)] ? f[a7(413)][a7(437)](new f[(a7(422))](D)) : function(J, a8, K) { 312 | for (a8 = a7, 313 | J[a8(456)](), 314 | K = 0; K < J.length; J[K] === J[K + 1] ? J[a8(390)](K + 1, 1) : K += 1) 315 | ; 316 | return J 317 | }(D), 318 | E = 'nAsAaAb'.split('A'), 319 | E = E[a7(375)][a7(440)](E), 320 | F = 0; F < D[a7(438)]; G = D[F], 321 | H = v(f, z, G), 322 | E(H) ? (I = H === 's' && !f[a7(418)](z[G]), 323 | a7(378) === A + G ? C(A + G, H) : I || C(A + G, z[G])) : C(A + G, H), 324 | F++) 325 | ; 326 | return B; 327 | function C(J, K, a6) { 328 | a6 = b, 329 | Object[a6(400)][a6(454)].call(B, K) || (B[K] = []), 330 | B[K][a6(393)](J) 331 | } 332 | } 333 | , 334 | y(); 335 | function a(ad) { 336 | return ad = '0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10;onreadystatechange;application/json;undefined;display: none;object;_cf_chl_opt;charAt;from;length;pow;bind;send;tabIndex;Content-Type;contentDocument;call;boolean;1749meMeVf;replace;[native code];timeout;getOwnPropertyNames;number;removeChild;hasOwnProperty;createElement;sort;charCodeAt;msg;getPrototypeOf;%2b;addEventListener;Content-type;44jVXaeo;__CF$cv$params;3172698fewaEA;Message: ;iframe;ontimeout;loading;catch;cFPWv;symbol;6921530pKEFzO;includes;/0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10/;POST;d.cookie;Object;/beacon/ov;1528YJCEGG;/cv/result/;4016270ZKDoub;xhHjpm;document;fromCharCode;string;61767eGTCHg;KG2A0eRcvEsHMjm1LlXfz$qZdV8aD+7BytrU-C6unWIkb3JON5wxoTFS9i4ghYQPp;splice;random;isArray;push;concat;1497095mYMkKq;appendChild;Microsoft.XMLHTTP;Function;giZRRiQTBy;prototype;XMLHttpRequest;error on cf_chl_props;function;open;join;toString;23475WbNMZJ;setRequestHeader;application/x-www-form-urlencoded;1866CuNRNs;ActiveXObject;/cdn-cgi/challenge-platform/h/;Array;body;stringify;clientInformation;bigint;isNaN;style;DOMContentLoaded; - ;Set;contentWindow;/invisible/jsd;4HUmwuG;Error object: ;keys;navigator'.split(';'), 337 | a = function() { 338 | return ad 339 | } 340 | , 341 | a() 342 | } 343 | function l(f, z, a1, A, B, C, D, E, F, G) { 344 | if (a1 = S, 345 | !j(.01)) 346 | return ![]; 347 | A = [a1(367) + f, a1(426) + JSON[a1(415)](z)][a1(405)](a1(421)); 348 | try { 349 | if (B = g[a1(365)], 350 | C = a1(412) + g[a1(435)][a1(372)] + a1(380) + 1 + a1(376) + B.r + a1(424), 351 | D = n(), 352 | !D) 353 | return; 354 | E = a1(377), 355 | D[a1(404)](E, C, !![]), 356 | a1(450)in D && (D[a1(450)] = 2500, 357 | D[a1(369)] = function() {} 358 | ), 359 | D[a1(408)](a1(462), a1(409)), 360 | F = {}, 361 | F[a1(458)] = A, 362 | G = i[a1(384)](JSON[a1(415)](F))[a1(448)]('+', a1(460)), 363 | D[a1(441)]('v_' + B.r + '=' + G) 364 | } catch (H) {} 365 | } 366 | function y(ab, c, e, f) { 367 | (ab = S, 368 | c = ![], 369 | e = function(aa, z, A) { 370 | if (aa = b, 371 | !c) { 372 | if (c = !![], 373 | z = g[aa(365)], 374 | !z) 375 | return; 376 | A = x(), 377 | k(z.r, A.r), 378 | A.e && l(aa(402), A.e) 379 | } 380 | } 381 | , 382 | h.readyState !== 'loading') ? e() : g[ab(461)] ? h[ab(461)](ab(420), e) : (f = h[ab(430)] || function() {} 383 | , 384 | h[ab(430)] = function(ac) { 385 | ac = ab, 386 | f(), 387 | h.readyState !== ac(370) && (h[ac(430)] = f, 388 | e()) 389 | } 390 | ) 391 | } 392 | function x(a9, z, A, B, C, D) { 393 | a9 = S; 394 | try { 395 | return z = h[a9(455)](a9(368)), 396 | z[a9(419)] = a9(433), 397 | z[a9(442)] = '-1', 398 | h[a9(414)][a9(396)](z), 399 | A = z[a9(423)], 400 | B = {}, 401 | B = giZRRiQTBy(A, A, '', B), 402 | B = giZRRiQTBy(A, A[a9(416)] || A[a9(428)], 'n.', B), 403 | B = giZRRiQTBy(A, z[a9(444)], 'd.', B), 404 | h[a9(414)][a9(453)](z), 405 | C = {}, 406 | C.r = B, 407 | C.e = null, 408 | C 409 | } catch (E) { 410 | return D = {}, 411 | D.r = {}, 412 | D.e = E, 413 | D 414 | } 415 | } 416 | function m(c) {} 417 | function v(f, z, A, a4, B) { 418 | a4 = S; 419 | try { 420 | return z[A][a4(371)](function() {}), 421 | 'p' 422 | } catch (C) {} 423 | try { 424 | if (null == z[A]) 425 | return z[A] === void 0 ? 'u' : 'x' 426 | } catch (D) { 427 | return 'i' 428 | } 429 | return f[a4(413)][a4(392)](z[A]) ? 'a' : z[A] === f[a4(413)] ? 'D' : (B = typeof z[A], 430 | a4(403) == B ? u(f, z[A]) ? 'N' : 'f' : s[B] || '?') 431 | } 432 | function w(c, a5, e) { 433 | for (a5 = S, 434 | e = []; null !== c; e = e[a5(394)](Object[a5(427)](c)), 435 | c = Object[a5(459)](c)) 436 | ; 437 | return e 438 | } 439 | function n(a2) { 440 | if (a2 = S, 441 | g[a2(401)]) 442 | return new g[(a2(401))](); 443 | if (g[a2(411)]) 444 | try { 445 | return new g[(a2(411))](a2(397)) 446 | } catch (c) {} 447 | } 448 | function u(c, e, a3) { 449 | return a3 = S, 450 | e instanceof c[a3(398)] && 0 < c[a3(398)][a3(400)][a3(406)][a3(445)](e).indexOf(a3(449)) 451 | } 452 | function b(c, d, e) { 453 | return e = a(), 454 | b = function(f, g, h) { 455 | return f = f - 364, 456 | h = e[f], 457 | h 458 | } 459 | , 460 | b(c, d) 461 | } 462 | function j(c, Z) { 463 | return Z = S, 464 | Math[Z(391)]() < c 465 | } 466 | function k(c, e, a0, f, z) { 467 | a0 = S, 468 | f = { 469 | 'wp': i[a0(384)](JSON[a0(415)](e)), 470 | 's': a0(429) 471 | }, 472 | z = new XMLHttpRequest(), 473 | z[a0(404)](a0(377), a0(412) + g[a0(435)][a0(372)] + a0(382) + c), 474 | z[a0(408)](a0(443), a0(431)), 475 | z[a0(441)](JSON[a0(415)](f)) 476 | } 477 | }() 478 | -------------------------------------------------------------------------------- /dev_stuff/nowsecure.nl_analysis/v1ray=________________.js: -------------------------------------------------------------------------------- 1 | // called from html: 2 | (function() { 3 | window._cf_chl_opt = { 4 | cvId: '2', 5 | cZone: 'nowsecure.nl', 6 | cType: 'managed', 7 | cNounce: '27030', 8 | cRay: '7ddf8bfc69e0ba92', 9 | cHash: '2ba3e6c3364659b', 10 | cUPMDTk: "\/?__cf_chl_tk=1hwWc.Wp5ioCReGtMuaNmrPDZTNGd.emn8K5ywWt9u4-1687888378-0-gaNycGzNDKU", 11 | cFPWv: 'g', 12 | cTTimeMs: '1000', 13 | cMTimeMs: '0', 14 | cTplV: 5, 15 | cTplB: 'cf', 16 | cK: "", 17 | cRq: { 18 | ru: 'aHR0cHM6Ly9ub3dzZWN1cmUubmwv', 19 | ra: 'TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExNC4wLjAuMCBTYWZhcmkvNTM3LjM2', 20 | rm: 'R0VU', 21 | d: 'rFZ05zjvQK7f24chSRFsDoPEy8RbhQ33YjgggLBH2HsBm1hH0vLjggm257xDYM7nL1cc2TqAhAsCNuJzyeF7GOxl9QiDLHHec7kz1VxPpGzpzdB2udfwMKmUZSg0NZtYmmL1KNmIqkIZ77uc3zlqNwpFfQ16j1CYevThhwW9bq5xZh0GKPiSUNEycSxA3spRwTdFq9kutVRaPpsKenozNjepiD+2o1QTxIe0pMXttHHsITqAZ6IUyh5wPLghEJD/0ZY23dVJVoZ/1ZnWx7jPjYo6IeWxPBcTEzm9lKwwgc23bY7pCmL9c/Cc10TAdhatCs2Sf1umU2PCjyPFjWj6IadyQZ6AndXDlesDoW+gMXT+aPpN3tD93MZKt+nyoX9RUUVYFFs6oSlQqDMvMcDZBlzUasW0xtyMvreFiIYY1pKRCBGJqB1UvHB+Rj+WUVqfxzT6a5WLf0spqmS0Q+kxWVtSD/BV2g/ap1g3GMeumKSr+N4SVzS6+kgpk8O6FPNbu7t/ZCqzj156zshrL+UwRij2lEOkCexj9eTCp0hawz7VXa8X8Jvw4xiD5lzFngxu+iiV/P2TKZJqzYyovSwBWA==', 22 | t: 'MTY4Nzg4ODM3OC4zMDcwMDA=', 23 | cT: 1687893326, // Math.floor(Date.now() / 1000), 24 | m: 'i8NtMxwZqnQGEgIISvC4+1I1l4r5HknUvp5mOjx+qeM=', 25 | i1: '0EakX8xtUj9/l1pkQuiG6A==', 26 | i2: 'mmaXyHuIDNxYpKibGYp4nA==', 27 | zh: '9o6sDYLEg3t9KrPhDrxQpLX3EFuiRPt8/vBjYeXXjhM=', 28 | uh: '0CF8H7M40rTpO3nQlHachgfkgE8HRlszEVlfLXI8vE4=', 29 | hh: 'ZWKc/9OeKWBypS27gOf1uCLya4SW2C0xp63VDfZjYWk=', 30 | } 31 | }; 32 | var trkjs = document.createElement('img'); 33 | trkjs.setAttribute('src', '/cdn-cgi/images/trace/managed/js/transparent.gif?ray=7ddf8bfc69e0ba92'); 34 | trkjs.setAttribute('alt', ''); 35 | trkjs.setAttribute('style', 'display: none'); 36 | //document.body.appendChild(trkjs); 37 | // add transparent 1x1 GIF 38 | 39 | var cpo = document.createElement('script'); 40 | cpo.src = '/cdn-cgi/challenge-platform/h/g/orchestrate/managed/v1?ray=7ddf8bfc69e0ba92'; 41 | // build `v1?ray=****************.js` 42 | 43 | // => '' 44 | window._cf_chl_opt.cOgUHash = /*location.hash =>*/ '' === '' && /*location.href.indexOf('#') =>*/ -1 !== -1 ? '#' : /*location.hash =>*/ ''; 45 | window._cf_chl_opt.cOgUQuery = location.search === '' && location.href.slice(0, location.href.length - window._cf_chl_opt.cOgUHash.length).indexOf('?') !== -1 ? '?' : location.search; 46 | 47 | if (window.history && window.history.replaceState) { 48 | var ogU = location.pathname + window._cf_chl_opt.cOgUQuery + window._cf_chl_opt.cOgUHash; 49 | history.replaceState(null, null, "\/?__cf_chl_rt_tk=1hwWc.Wp5ioCReGtMuaNmrPDZTNGd.emn8K5ywWt9u4-1687888378-0-gaNycGzNDKU" + window._cf_chl_opt.cOgUHash); 50 | cpo.onload = function() { 51 | history.replaceState(null, null, ogU); 52 | } 53 | ; 54 | } 55 | //document.getElementsByTagName('head')[0].appendChild(cpo); 56 | // load (execute) `v1?ray=****************.js` 57 | }()); 58 | 59 | window._cf_chl_opt = { 60 | cFPWv: 'g' 61 | }; 62 | ~function(g, h, i, o, s) { 63 | // MAIN function 64 | 65 | // global props 66 | g = window, 67 | h = window.document, 68 | 69 | // assumption: I['xhHjpm'] is encryptor 70 | i = function(b, e, f, z) { 71 | e = String.fromCharCode, 72 | f = { 73 | 'h': function(A) { 74 | return A == null ? '' : f.g(A, 6, function(B, b) { 75 | return b = b, 76 | 'KG2A0eRcvEsHMjm1LlXfz$qZdV8aD+7BytrU-C6unWIkb3JON5wxoTFS9i4ghYQPp'.charAt(B) 77 | }) 78 | }, 79 | 'g': function(A, B, C, b, D, E, F, G, H, I, J, K, L, M, N, O, P, Q) { 80 | if (b = b, 81 | null == A) 82 | return ''; 83 | for (E = {}, 84 | F = {}, 85 | G = '', 86 | H = 2, 87 | I = 3, 88 | J = 2, 89 | K = [], 90 | L = 0, 91 | M = 0, 92 | N = 0; N < A.length; N += 1) 93 | if (O = A.charAt(N), 94 | Object.prototype.hasOwnProperty.call(E, O) || (E[O] = I++, 95 | F[O] = !0), 96 | P = G + O, 97 | Object.prototype.hasOwnProperty.call(E, P)) 98 | G = P; 99 | else { 100 | if (Object.prototype.hasOwnProperty.call(F, G)) { 101 | if (256 > G.charCodeAt(0)) { 102 | for (D = 0; D < J; L <<= 1, 103 | M == B - 1 ? (M = 0, 104 | K.push(C(L)), 105 | L = 0) : M++, 106 | D++) 107 | ; 108 | for (Q = G.charCodeAt(0), 109 | D = 0; 8 > D; L = Q & 1 | L << 1, 110 | M == B - 1 ? (M = 0, 111 | K.push(C(L)), 112 | L = 0) : M++, 113 | Q >>= 1, 114 | D++) 115 | ; 116 | } else { 117 | for (Q = 1, 118 | D = 0; D < J; L = L << 1 | Q, 119 | B - 1 == M ? (M = 0, 120 | K.push(C(L)), 121 | L = 0) : M++, 122 | Q = 0, 123 | D++) 124 | ; 125 | for (Q = G.charCodeAt(0), 126 | D = 0; 16 > D; L = L << 1 | Q & 1.22, 127 | B - 1 == M ? (M = 0, 128 | K.push(C(L)), 129 | L = 0) : M++, 130 | Q >>= 1, 131 | D++) 132 | ; 133 | } 134 | H--, 135 | 0 == H && (H = Math.pow(2, J), 136 | J++), 137 | delete F[G] 138 | } else 139 | for (Q = E[G], 140 | D = 0; D < J; L = 1.06 & Q | L << 1, 141 | B - 1 == M ? (M = 0, 142 | K.push(C(L)), 143 | L = 0) : M++, 144 | Q >>= 1, 145 | D++) 146 | ; 147 | G = (H--, 148 | H == 0 && (H = Math.pow(2, J), 149 | J++), 150 | E[P] = I++, 151 | String(O)) 152 | } 153 | if (G !== '') { 154 | if (Object.prototype.hasOwnProperty.call(F, G)) { 155 | if (256 > G.charCodeAt(0)) { 156 | for (D = 0; D < J; L <<= 1, 157 | B - 1 == M ? (M = 0, 158 | K.push(C(L)), 159 | L = 0) : M++, 160 | D++) 161 | ; 162 | for (Q = G.charCodeAt(0), 163 | D = 0; 8 > D; L = L << 1 | Q & 1, 164 | B - 1 == M ? (M = 0, 165 | K.push(C(L)), 166 | L = 0) : M++, 167 | Q >>= 1, 168 | D++) 169 | ; 170 | } else { 171 | for (Q = 1, 172 | D = 0; D < J; L = L << 1 | Q, 173 | M == B - 1 ? (M = 0, 174 | K.push(C(L)), 175 | L = 0) : M++, 176 | Q = 0, 177 | D++) 178 | ; 179 | for (Q = G.charCodeAt(0), 180 | D = 0; 16 > D; L = L << 1 | Q & 1.84, 181 | M == B - 1 ? (M = 0, 182 | K.push(C(L)), 183 | L = 0) : M++, 184 | Q >>= 1, 185 | D++) 186 | ; 187 | } 188 | H--, 189 | H == 0 && (H = Math.pow(2, J), 190 | J++), 191 | delete F[G] 192 | } else 193 | for (Q = E[G], 194 | D = 0; D < J; L = L << 1 | 1.45 & Q, 195 | B - 1 == M ? (M = 0, 196 | K.push(C(L)), 197 | L = 0) : M++, 198 | Q >>= 1, 199 | D++) 200 | ; 201 | H--, 202 | H == 0 && J++ 203 | } 204 | for (Q = 2, 205 | D = 0; D < J; L = 1 & Q | L << 1.3, 206 | M == B - 1 ? (M = 0, 207 | K.push(C(L)), 208 | L = 0) : M++, 209 | Q >>= 1, 210 | D++) 211 | ; 212 | for (; ; ) 213 | if (L <<= 1, 214 | B - 1 == M) { 215 | K.push(C(L)); 216 | break 217 | } else 218 | M++; 219 | return K.join('') 220 | }, 221 | 'j': function(A, b) { 222 | return b = b, 223 | A == null ? '' : '' == A ? null : f.i(A.length, 32768, function(B, b) { 224 | return b = b, 225 | A.charCodeAt(B) 226 | }) 227 | }, 228 | 'i': function(A, B, C, b, D, E, F, G, H, I, J, K, L, M, N, O, Q, P) { 229 | for (b = b, 230 | D = [], 231 | E = 4, 232 | F = 4, 233 | G = 3, 234 | H = [], 235 | K = C(0), 236 | L = B, 237 | M = 1, 238 | I = 0; 3 > I; D[I] = I, 239 | I += 1) 240 | ; 241 | for (N = 0, 242 | O = Math.pow(2, 2), 243 | J = 1; J != O; P = L & K, 244 | L >>= 1, 245 | 0 == L && (L = B, 246 | K = C(M++)), 247 | N |= (0 < P ? 1 : 0) * J, 248 | J <<= 1) 249 | ; 250 | switch (N) { 251 | case 0: 252 | for (N = 0, 253 | O = Math.pow(2, 8), 254 | J = 1; O != J; P = K & L, 255 | L >>= 1, 256 | 0 == L && (L = B, 257 | K = C(M++)), 258 | N |= J * (0 < P ? 1 : 0), 259 | J <<= 1) 260 | ; 261 | Q = e(N); 262 | break; 263 | case 1: 264 | for (N = 0, 265 | O = Math.pow(2, 16), 266 | J = 1; J != O; P = K & L, 267 | L >>= 1, 268 | L == 0 && (L = B, 269 | K = C(M++)), 270 | N |= (0 < P ? 1 : 0) * J, 271 | J <<= 1) 272 | ; 273 | Q = e(N); 274 | break; 275 | case 2: 276 | return '' 277 | } 278 | for (I = D[3] = Q, 279 | H.push(Q); ; ) { 280 | if (M > A) 281 | return ''; 282 | for (N = 0, 283 | O = Math.pow(2, G), 284 | J = 1; O != J; P = K & L, 285 | L >>= 1, 286 | L == 0 && (L = B, 287 | K = C(M++)), 288 | N |= J * (0 < P ? 1 : 0), 289 | J <<= 1) 290 | ; 291 | switch (Q = N) { 292 | case 0: 293 | for (N = 0, 294 | O = Math.pow(2, 8), 295 | J = 1; O != J; P = L & K, 296 | L >>= 1, 297 | 0 == L && (L = B, 298 | K = C(M++)), 299 | N |= (0 < P ? 1 : 0) * J, 300 | J <<= 1) 301 | ; 302 | D[F++] = e(N), 303 | Q = F - 1, 304 | E--; 305 | break; 306 | case 1: 307 | for (N = 0, 308 | O = Math.pow(2, 16), 309 | J = 1; J != O; P = L & K, 310 | L >>= 1, 311 | L == 0 && (L = B, 312 | K = C(M++)), 313 | N |= J * (0 < P ? 1 : 0), 314 | J <<= 1) 315 | ; 316 | D[F++] = e(N), 317 | Q = F - 1, 318 | E--; 319 | break; 320 | case 2: 321 | return H.join('') 322 | } 323 | if (0 == E && (E = Math.pow(2, G), 324 | G++), 325 | D[Q]) 326 | Q = D[Q]; 327 | else if (F === Q) 328 | Q = I + I.charAt(0); 329 | else 330 | return null; 331 | H.push(Q), 332 | D[F++] = I + Q.charAt(0), 333 | E--, 334 | I = Q, 335 | 0 == E && (E = Math.pow(2, G), 336 | G++) 337 | } 338 | } 339 | } 340 | return {'xhHjpm':f.h} 341 | }() 342 | 343 | , 344 | // types - dict, no usage? 345 | s = {}, 346 | s.object = 'o', 347 | s.string = 's', 348 | s.undefined = 'u', 349 | s.symbol = 'z', 350 | s.number = 'n', 351 | s.bigint = 'I', 352 | s.boolean = 'b', 353 | 354 | // on document loaded, // first called function 355 | function y() { 356 | (c = false, 357 | 358 | // check window['__CF$cv$params'] 359 | e = function() { 360 | if (true) { 361 | if (c = true, 362 | z = window['__CF$cv$params'], 363 | !z) 364 | return; 365 | A = x(), // iframe test 366 | k(z.r, A.r), // post to server 367 | A.e && l('error on cf_chl_props', A.e) // send error back to server 368 | } 369 | } 370 | , 371 | window.document.readyState !== 'loading') ? e() : window.addEventListener ? window.document.addEventListener('DOMContentLoaded', e) : (f = window.document.onreadystatechange || function() {} 372 | , 373 | window.document.onreadystatechange = function() { 374 | b = b, 375 | f(), 376 | window.document.readyState !== 'loading' && (window.document.onreadystatechange = f, 377 | e()) 378 | } 379 | ) 380 | }() 381 | 382 | 383 | 384 | 385 | 386 | // DE-OBFUSCATION 387 | // DE-OBFUSCATION 388 | 389 | // string based on int 390 | function b(c) { // returns string from `function a(ad)` 391 | e = ['44jVXaeo', '__CF$cv$params', '3172698fewaEA', 'Message: ', 'iframe', 'ontimeout', 'loading', 'catch', 'cFPWv', 'symbol', '6921530pKEFzO', 'includes', '/0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10/', 'POST', 'd.cookie', 'Object', '/beacon/ov', '1528YJCEGG', '/cv/result/', '4016270ZKDoub', 'xhHjpm', 'document', 'fromCharCode', 'string', '61767eGTCHg', 'KG2A0eRcvEsHMjm1LlXfz$qZdV8aD+7BytrU-C6unWIkb3JON5wxoTFS9i4ghYQPp', 'splice', 'random', 'isArray', 'push', 'concat', '1497095mYMkKq', 'appendChild', 'Microsoft.XMLHTTP', 'Function', 'giZRRiQTBy', 'prototype', 'XMLHttpRequest', 'error on cf_chl_props', 'function', 'open', 'join', 'toString', '23475WbNMZJ', 'setRequestHeader', 'application/x-www-form-urlencoded', '1866CuNRNs', 'ActiveXObject', '/cdn-cgi/challenge-platform/h/', 'Array', 'body', 'stringify', 'clientInformation', 'bigint', 'isNaN', 'style', 'DOMContentLoaded', ' - ', 'Set', 'contentWindow', '/invisible/jsd', '4HUmwuG', 'Error object: ', 'keys', 'navigator', '0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10', 'onreadystatechange', 'application/json', 'undefined', 'display: none', 'object', '_cf_chl_opt', 'charAt', 'from', 'length', 'pow', 'bind', 'send', 'tabIndex', 'Content-Type', 'contentDocument', 'call', 'boolean', '1749meMeVf', 'replace', '[native code]', 'timeout', 'getOwnPropertyNames', 'number', 'removeChild', 'hasOwnProperty', 'createElement', 'sort', 'charCodeAt', 'msg', 'getPrototypeOf', '%2b', 'addEventListener', 'Content-type'] 392 | return e[c- 364] 393 | } 394 | 395 | , // does something with fuction a() => function b(c) => {retrun string[c]} 396 | function(c) { 397 | for (b = b, 398 | f = c(); true; ) 399 | try { 400 | if (true) 401 | break; 402 | else 403 | f.push(f.shift()) 404 | } catch (A) { 405 | f.push(f.shift()) 406 | } 407 | }(a /*function*/), 408 | 409 | // END DE-OBFUSCATION 410 | // END DE-OBFUSCATION 411 | 412 | 413 | 414 | 415 | // NETWORK 416 | // NETWORK 417 | 418 | // return new window.XMLHttpRequest() 419 | function n(b) { 420 | if (b = b, 421 | window.XMLHttpRequest) 422 | return new window.XMLHttpRequest(); 423 | if (window.ActiveXObject) 424 | try { 425 | return new window.ActiveXObject('Microsoft.XMLHTTP') 426 | } catch (c) {} 427 | } 428 | 429 | // POST args[0]:f,args[1]:z to '/cdn-cgi/challenge-platform/h/g/beacon/ov1/0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10/' + args[2]:b.r + '/invisible/jsd' 430 | // assumption: ecryption using i['xhHjpm'] 431 | // assumption: Sends Error-messages to server 432 | function l(f, z, b, A, B, C, D, E, F, G) { 433 | if (b = b, 434 | !(Math.random() < .01) 435 | return false; 436 | A = ['Message: ' + f, 'Error object: ' + JSON.stringify(z)].join(' - '); 437 | try { 438 | if (B = window['__CF$cv$params'], 439 | C = '/cdn-cgi/challenge-platform/h/' + window['_cf_chl_opt']['cFPWv'] /*"g"*/ + '/beacon/ov' + 1 + '/0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10/' + B.r + '/invisible/jsd', 440 | D = n(), // = new window.XMLHttpRequest() 441 | !D) 442 | return; 443 | E = 'POST', 444 | D.open(E, C, !![]), 445 | 'timeout'in D && (D.timeout = 2500, 446 | D.ontimeout = function() {} 447 | ), 448 | D.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'), 449 | F = {}, 450 | F['msg'] = A, 451 | G = i['xhHjpm'](JSON.stringify(F)).replace('+', '%2b'), 452 | D.send('v_' + B.r + '=' + G) 453 | } catch (H) {} 454 | } 455 | 456 | // POST args[1]:e to "/cdn-cgi/challenge-platform/h/g/cv/result/"+args[0]:c 457 | // assumption: ecryption using i['xhHjpm'] 458 | function k(c, e, b, f, z) { 459 | b = b, 460 | f = { 461 | 'wp': i['xhHjpm'](JSON.stringify(e)), 462 | 's': '0.855424979613877:1687885557:ANZuQxetpZPdIQEsSfYeU2ColQYy0UezvgDBHkCbQ10' 463 | }, 464 | z = new XMLHttpRequest(), 465 | z.open('POST', '/cdn-cgi/challenge-platform/h/' + window['_cf_chl_opt']['cFPWv'] /*"g"*/ + '/cv/result/' + c), 466 | z.setRequestHeader('Content-Type', 'application/json'), 467 | z.send(JSON.stringify(f)) 468 | } 469 | 470 | // END NETWORK 471 | // END NETWORK 472 | 473 | 474 | // 1. create iframe 475 | // 2. process(iframe.clientInformation || iframe.navigator) 476 | // 3. retrun processed iframe.clientInformation || iframe.navigator 477 | function x() { 478 | try { 479 | return z = window.document.createElement('iframe'), 480 | z.style = 'display: none', 481 | z.tabIndex = '-1', 482 | window.document.body.appendChild(z), 483 | A = z.contentWindow, // = globalThis of iframe 484 | B = {}, 485 | B = giZRRiQTBy(A, A, '', B), 486 | B = giZRRiQTBy(A, A.clientInformation || A.navigator, 'n.', B), 487 | B = giZRRiQTBy(A, z.contentDocument, 'd.', B), 488 | window.document.body.removeChild(z), 489 | C = {}, 490 | C.r = B, 491 | C.e = null, 492 | C 493 | } catch (E) { 494 | return D = {}, 495 | D.r = {}, 496 | D.e = E, 497 | D 498 | } 499 | } 500 | 501 | // empty function, no usage? 502 | function m(c) {} 503 | 504 | 505 | 506 | // OVERWRITE PROTECTION 507 | // OVERWRITE PROTECTION 508 | 509 | // assumption: check overwritten values? 510 | // args[0]:f = globalThis 511 | // args[1]:z:Object? 512 | // args[2]:A some_str 513 | // args[3]:B = {} in usages 514 | window['giZRRiQTBy'] = function(f, z, A, B) { 515 | if (z === null || void 0 === z) 516 | return B; 517 | for (D = w(z), // = keys of z 518 | f.Object.getOwnPropertyNames && (D = D.concat(f.Object.getOwnPropertyNames(z))), 519 | 520 | D = f.Array.from && f.Set ? f.Array.from(new f.Set(D)) : function(J) 521 | { 522 | for (b = b, 523 | J.sort(), 524 | K = 0; K < J.length; J[K] === J[K + 1] ? J.splice(K + 1, 1) : K += 1) 525 | ; 526 | return J 527 | }(D), 528 | 529 | E = ['n', 's', 'a', 'b'], 530 | E = E.includes.bind(E), // function (arg includes ['n', 's', 'a', 'b'] ?) 531 | F = 0; F < D.length; G = D[F], 532 | H = v(f, z, G), 533 | E(H) ? (I = H === 's' && !f.isNaN(z[G]), 534 | 'd.cookie' === A + G ? C(A + G, H) : I || C(A + G, z[G])) : C(A + G, H), 535 | F++) 536 | ; 537 | return B; 538 | 539 | // .hasOwnProperty 540 | // B from outer scope 541 | function C(J, K) { 542 | Object.prototype.hasOwnProperty.call(B, K) || (B[K] = []), 543 | B[K].push(J) 544 | } 545 | } 546 | 547 | // assumption: check overwritten values 548 | // B from outer scope 549 | function v(f, z, A) { 550 | try { // bind catch of z[A] to empty function 551 | return z[A].catch(function() {}), 552 | 'p' 553 | } catch (C) {} 554 | try { 555 | if (null == z[A]) 556 | return z[A] === void 0 ? 'u' : 'x' 557 | } catch (D) { 558 | return 'i' 559 | } 560 | return f.Array.isArray(z[A]) ? 'a' : z[A] === f.Array ? 'D' : (B = typeof z[A], 561 | 'function' == B ? u(f, z[A]) ? 'N' : 'f' : s[B] || '?') 562 | } 563 | 564 | // get all keys of object => return [key1, key2, ...] 565 | // checck .getPrototypeOf() correlates with .keys() ? 566 | function w(c) { 567 | for (b = b, 568 | e = []; null !== c; e = e.concat(Object.keys(c)), 569 | c = Object.getPrototypeOf(c)); 570 | 571 | return e 572 | } 573 | 574 | // assumption: check function 575 | // args[0]: native function to check 576 | // args[1]: globalThis 577 | function u(c, e) { 578 | e instanceof c.Function && 0 < c.Function.prototype.toString.call(e).indexOf('[native code]') 579 | } 580 | 581 | // END OVERWRITE PROTECTION 582 | // END OVERWRITE PROTECTION 583 | 584 | // end of MAIN 585 | }() 586 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=46.4.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | requests 3 | selenium-interceptor 4 | undetected-chromedriver 5 | selenium-wire 6 | webdriver-manager 7 | selenium-injector 8 | 9 | # google-colab 10 | # PyVirtualDisplay 11 | # google-colab-shell 12 | 13 | # dev 14 | setuptools 15 | twine -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | version = attr: selenium_profiles.__version__ 3 | license_files = LICENSE.md 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | import sys 3 | 4 | 5 | requirements = ['selenium', 'requests', 'selenium-interceptor', 6 | "undetected-chromedriver", "selenium-wire", "webdriver-manager", 7 | "selenium-injector>=2.3"] 8 | 9 | if 'google.colab' in sys.modules: # we're on google-colab 10 | requirements.extend(['PyVirtualDisplay', "google-colab-shell"]) 11 | 12 | with open('README.md', 'r', encoding='utf-8') as fh: 13 | long_description = fh.read() 14 | 15 | setuptools.setup( 16 | name='selenium_profiles', 17 | author='Aurin Aegerter', 18 | author_email='aurinliun@gmx.ch', 19 | description='Emulate and Automate Chrome using Profiles and Selenium', 20 | keywords='Selenium,emulation, automation, undetected-chromedriver, webautomation', 21 | long_description=long_description, 22 | long_description_content_type='text/markdown', 23 | url='https://github.com/kaliiiiiiiiii/Selenium_Profiles', 24 | project_urls={ 25 | 'Documentation': 'https://github.com/kaliiiiiiiiii/Selenium_Profiles', 26 | 'Bug Reports': 27 | 'https://github.com/kaliiiiiiiiii/Selenium_Profiles/issues', 28 | 'Source Code': 'https://github.com/kaliiiiiiiiii/Selenium_Profiles', 29 | }, 30 | package_dir={'': 'src'}, 31 | packages=setuptools.find_packages(where='src'), 32 | classifiers=[ 33 | # see https://pypi.org/classifiers/ 34 | 'Development Status :: 2 - Pre-Alpha', 35 | 'Intended Audience :: Developers', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.7', 38 | 'Programming Language :: Python :: 3.8', 39 | 'Programming Language :: Python :: 3.9', 40 | 'Programming Language :: Python :: 3.10', 41 | 'Programming Language :: Python :: 3.11', 42 | 'License :: Free for non-commercial use', 43 | 'Natural Language :: English', 44 | 'Operating System :: OS Independent', 45 | 'Framework :: Jupyter', 46 | 'Topic :: Internet', 47 | 'Topic :: Internet :: WWW/HTTP :: Browsers', 48 | 49 | ], 50 | python_requires='>=3.7', 51 | install_requires=requirements, 52 | include_package_data=True, 53 | extras_require={ 54 | 'dev': ['check-manifest'], 55 | # 'test': ['coverage'], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /src/selenium_profiles/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.2.10" 2 | -------------------------------------------------------------------------------- /src/selenium_profiles/files/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.2" 2 | -------------------------------------------------------------------------------- /src/selenium_profiles/files/tmp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Profiles/90d6d4e668c506e1e2d90bdfafff45d9df71e735/src/selenium_profiles/files/tmp/__init__.py -------------------------------------------------------------------------------- /src/selenium_profiles/js/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.2" 2 | -------------------------------------------------------------------------------- /src/selenium_profiles/js/export_profile.js: -------------------------------------------------------------------------------- 1 | try{var done = arguments[0]}catch{var done=undefined} // we're executing with selenium 2 | 3 | function roundHalf(num) { // round to half int 4 | if (typeof num === 'number') {return Math.round(num*2)/2} 5 | else {return null} 6 | }; 7 | 8 | function copyToClipboard(text){ 9 | window.prompt("Copy to clipboard: Ctrl+C, Enter", text); 10 | }; 11 | 12 | function is_done(value){ 13 | if(done){done(value)}else{copyToClipboard(JSON.stringify(value))} 14 | } 15 | 16 | function a(elem, replace=null){ // replace non_existing variables with null 17 | try{elem = eval(elem)} 18 | catch{elem = replace}; 19 | //if_exist, else null 20 | if (typeof elem !== 'undefined') {return elem} else {return replace}; 21 | }; 22 | 23 | function build_navigator(useragent) { 24 | wow64 = a(navigator.userAgent.indexOf('WOW64')>-1); 25 | 26 | maxtouchpoints = a('window.navigator.maxTouchPoints'); 27 | if (maxtouchpoints > 16 || maxtouchpoints < 1){maxtouchpoints=10} 28 | 29 | orient_type = a('screen.orientation.type') 30 | 31 | // fix orientation type Values 32 | if (orient_type === 'portrait-primary'){orient_type = "portraitPrimary"} 33 | if (orient_type === 'landscape-primary'){orient_type = "landscapePrimary"} 34 | 35 | my_screen = { 36 | "mobile":a('navigator.userAgentData.mobile'), 37 | "width": a('screen.width'), 38 | "height": a('screen.height'), 39 | "deviceScaleFactor": roundHalf(a('window.devicePixelRatio')), 40 | "screenOrientation": { 41 | "type": orient_type, 42 | "angle": a('screen.orientation.angle')}} 43 | 44 | my_navigator = { 45 | "cdp": { 46 | "touch": (('ontouchstart' in window) || (maxtouchpoints > 0)), 47 | "maxtouchpoints": maxtouchpoints, 48 | "emulation": my_screen, 49 | "cores":a("navigator.hardwareConcurrency"), 50 | "useragent": { 51 | "platform": a('navigator.platform'), 52 | "acceptLanguage":a('navigator.language') || a('navigator.userLanguage'), 53 | "userAgent": a('navigator.userAgent'), 54 | "userAgentMetadata": { 55 | "brands": useragent["brands"], 56 | "fullVersionList": useragent["fullVersionList"], 57 | "fullVersion": useragent["uaFullVersion"], 58 | "platform": useragent["platform"], 59 | "platformVersion": useragent["platformVersion"], 60 | "architecture": useragent["architecture"], 61 | "model": useragent["model"], 62 | "mobile": useragent["mobile"], 63 | "bitness": useragent["bitness"], 64 | "wow64": window.wow64} 65 | } 66 | } 67 | } 68 | return my_navigator 69 | } 70 | 71 | console.log('Getting Profile..') 72 | navigator.userAgentData.getHighEntropyValues( // get useragent 73 | ["architecture", 74 | "model", 75 | "platformVersion", 76 | "bitness", 77 | "uaFullVersion", 78 | "fullVersionList"]) 79 | .then((values) => { 80 | my_profile = build_navigator(values); 81 | console.log(my_profile) 82 | is_done(my_profile) 83 | }); 84 | -------------------------------------------------------------------------------- /src/selenium_profiles/js/fetch.js: -------------------------------------------------------------------------------- 1 | try{var done = arguments[0];}catch{var done = undefined} 2 | 3 | function buffer2hex (buffer) { 4 | return [...new Uint8Array (buffer)] 5 | .map (b => b.toString (16).padStart (2, "0")) 6 | .join (""); 7 | } 8 | 9 | function headers2dict(headers){ 10 | var my_dict = {}; 11 | for (var pair of headers.entries()) { 12 | my_dict[pair[0]] = pair[1]}; 13 | return my_dict} 14 | 15 | async function get(url, options){ 16 | try{ 17 | var response = await fetch(url, options); 18 | var buffer = await response.arrayBuffer() 19 | var hex = buffer2hex(buffer) 20 | var parsed = { 21 | "HEX":hex, 22 | "headers":headers2dict(response.headers), 23 | "ok":response.ok, 24 | "status_code":response.status, 25 | "redirected":response.redirected, 26 | "status_text":response.statusText, 27 | "type":response.type, 28 | "url":response.url 29 | }; 30 | var parsed = {"status":"200", "value":parsed} 31 | }catch(e){var parsed = {"status":"error", "stack":e.stack, "message":e.message}} 32 | return parsed; 33 | } 34 | 35 | function is_done(value){ 36 | if(done){done(value)}else{console.log(value)} 37 | } 38 | 39 | try{get(%s, %s).then((parsed) => {is_done(parsed)})}catch(e){is_done(e)} -------------------------------------------------------------------------------- /src/selenium_profiles/js/undetected/__init.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Profiles/90d6d4e668c506e1e2d90bdfafff45d9df71e735/src/selenium_profiles/js/undetected/__init.py -------------------------------------------------------------------------------- /src/selenium_profiles/js/undetected/get_cdc_props.js: -------------------------------------------------------------------------------- 1 | let objectToInspect = window, 2 | result = []; 3 | while(objectToInspect !== null) 4 | { result = result.concat(Object.getOwnPropertyNames(objectToInspect)); 5 | objectToInspect = Object.getPrototypeOf(objectToInspect); } 6 | return result.filter(i => i.match(/.+_.+_(Array|Promise|Symbol)/ig)) -------------------------------------------------------------------------------- /src/selenium_profiles/js/undetected/navigator_webdriver.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(window, 'navigator', { 2 | value: new Proxy(navigator, { 3 | has: (target, key) => (key === 'webdriver' ? false : key in target), 4 | get: (target, key) => 5 | key === 'webdriver' ? 6 | false : 7 | typeof target[key] === 'function' ? 8 | target[key].bind(target) : 9 | target[key] 10 | }) 11 | }); -------------------------------------------------------------------------------- /src/selenium_profiles/js/undetected/remove_cdc_props.js: -------------------------------------------------------------------------------- 1 | let objectToInspect = window, 2 | result = []; 3 | while(objectToInspect !== null) 4 | { result = result.concat(Object.getOwnPropertyNames(objectToInspect)); 5 | objectToInspect = Object.getPrototypeOf(objectToInspect); } 6 | result.forEach(p => p.match(/.+_.+_(Array|Promise|Symbol)/ig) 7 | &&delete window[p]&&console.log('removed',p)) 8 | -------------------------------------------------------------------------------- /src/selenium_profiles/profiles/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "2.2" 2 | from selenium_profiles.profiles.profiles import Windows, Android -------------------------------------------------------------------------------- /src/selenium_profiles/profiles/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "Android": { 3 | "options": { 4 | "gpu": false 5 | }, 6 | "cdp": { 7 | "touch": true, 8 | "maxtouchpoints": 10, 9 | "cores": 8, 10 | "patch_version": true, 11 | "emulation": {"mobile":true,"width": 384, "height": 700, "deviceScaleFactor": 4, 12 | "screenOrientation": {"type": "portraitPrimary", "angle": 0}}, 13 | "useragent": { 14 | "platform": "Linux aarch64", 15 | "acceptLanguage":"en-US", 16 | "userAgent": "Mozilla/5.0 (Linux; Android 11; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", 17 | "userAgentMetadata": { 18 | "brands": [{"brand": "Google Chrome", "version": "105"}, {"brand": "Not)A;Brand", "version": "8"}, 19 | {"brand": "Chromium", "version": "105"}], 20 | "fullVersionList": [{"brand": "Google Chrome", "version": "105.0.5195.136"}, 21 | {"brand": "Not)A;Brand", "version": "8.0.0.0"}, 22 | {"brand": "Chromium", "version": "105.0.5195.136"}], 23 | "fullVersion": "105.0.5195.136", 24 | "platform": "Android", 25 | "platformVersion": "11.0.0", 26 | "architecture": "", 27 | "model": "HD1913", 28 | "mobile": true, 29 | "bitness": "", 30 | "wow64": false} 31 | } 32 | } 33 | }, 34 | "IOS": {}, 35 | "Windows": { 36 | "options": { 37 | "gpu": false 38 | }, 39 | "cdp": { 40 | "touch": true, 41 | "maxtouchpoints": 10, 42 | "cores": 8, 43 | "patch_version": true, 44 | "emulation": {"mobile":false,"width": 1024, "height": 648, "deviceScaleFactor": 1, 45 | "screenOrientation": {"type": "landscapePrimary", "angle": 0}}, 46 | "useragent": { 47 | "platform": "Win32", 48 | "acceptLanguage":"en-US", 49 | "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", 50 | "userAgentMetadata": { 51 | "brands": [{"brand":"Google Chrome","version":"107"}, 52 | {"brand":"Chromium","version":"107"}, 53 | {"brand":"Not=A?Brand","version":"24"}], 54 | "fullVersionList": [{"brand":"Google Chrome","version":"107.0.5304.88"}, 55 | {"brand":"Chromium","version":"107.0.5304.88"}, 56 | {"brand":"Not=A?Brand","version":"24.0.0.0"}], 57 | "fullVersion": "107.0.5304.88", 58 | "platform": "Windows", 59 | "platformVersion": "10.0.0", 60 | "architecture": "x86", 61 | "model": "", 62 | "mobile": false, 63 | "bitness": "64", 64 | "wow64": false} 65 | } 66 | } 67 | }, 68 | "Linux": {}, 69 | "Tablet": {} 70 | } -------------------------------------------------------------------------------- /src/selenium_profiles/profiles/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "options": { 3 | "sandbox": true, 4 | "headless": false, 5 | "load_images": true, 6 | "incognito": true, 7 | "app": false, 8 | "gpu": false, 9 | "proxy": null, 10 | "extension_paths": [], 11 | "auth_proxy": {"host":"host","port":9000,"username":"user", "password":"password"}, 12 | "args": [], 13 | "capabilities": [], 14 | "adb": false, 15 | "adb_package": "com.android.chrome" 16 | }, 17 | "cdp": { 18 | "pointer_as_touch": false, 19 | "darkmode": false, 20 | "touch": true, 21 | "maxtouchpoints": 5, 22 | "cdp_args": {}, 23 | "emulation": {"mobile":true,"width": 384, "height": 700, "deviceScaleFactor": 10, 24 | "screenOrientation": {"type": "portraitPrimary", "angle": 0}}, 25 | "useragent": { 26 | "platform": "Linux aarch64", 27 | "acceptLanguage":"en-US", 28 | "userAgent": "Mozilla/5.0 (Linux; Android 11; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Mobile Safari/537.36", 29 | "userAgentMetadata": { 30 | "brands": [{"brand": "Google Chrome", "version": "105"}, {"brand": "Not)A;Brand", "version": "8"}, 31 | {"brand": "Chromium", "version": "105"}], 32 | "fullVersionList": [{"brand": "Google Chrome", "version": "105.0.5195.136"}, 33 | {"brand": "Not)A;Brand", "version": "8.0.0.0"}, 34 | {"brand": "Chromium", "version": "105.0.5195.136"}], 35 | "fullVersion": "105.0.5195.136", 36 | "platform": "Android", 37 | "platformVersion": "11.0.0", 38 | "architecture": "", 39 | "model": "HD1913", 40 | "mobile": true, 41 | "bitness": "", 42 | "wow64": false} 43 | }} 44 | } 45 | -------------------------------------------------------------------------------- /src/selenium_profiles/profiles/profiles.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | def return_profile(platform: str): 4 | from selenium_profiles.utils.utils import read_json 5 | profile = defaultdict(lambda: None) 6 | profile.update(read_json(filename="profiles/default.json")[platform]) 7 | return profile # yet supported: "Android", "Windows" 8 | 9 | 10 | 11 | def Windows(): 12 | return return_profile("Windows") 13 | 14 | 15 | # noinspection PyPep8Naming 16 | def Android(): 17 | return return_profile("Android") 18 | 19 | def example(): 20 | from selenium_profiles.utils.utils import read_json 21 | example_profile = defaultdict(lambda: None ) 22 | example_profile.update(read_json(filename="profiles/example.json")) 23 | return example_profile 24 | 25 | def empty(): 26 | profile = defaultdict(lambda: None) 27 | # noinspection PyTypeChecker 28 | profile.update({"options":{}, "cdp":{}}) 29 | return profile 30 | -------------------------------------------------------------------------------- /src/selenium_profiles/profiles/scratch.json: -------------------------------------------------------------------------------- 1 | { 2 | "cdp": { 3 | "touch": true, 4 | "maxtouchpoints": 1, 5 | "emulation": { 6 | "mobile": true, 7 | "width": 385, 8 | "height": 833, 9 | "deviceScaleFactor": 4, 10 | "screenOrientation": { 11 | "type": "portraitPrimary", 12 | "angle": 0 13 | } 14 | }, 15 | "useragent": { 16 | "platform": "Linux aarch64", 17 | "userAgent": "Mozilla/5.0 (Linux; Android 11; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36", 18 | "userAgentMetadata": { 19 | "brands": [ 20 | { 21 | "brand": "Not?A_Brand", 22 | "version": "8" 23 | }, 24 | { 25 | "brand": "Chromium", 26 | "version": "108" 27 | }, 28 | { 29 | "brand": "Google Chrome", 30 | "version": "108" 31 | } 32 | ], 33 | "fullVersionList": [ 34 | { 35 | "brand": "Not?A_Brand", 36 | "version": "8.0.0.0" 37 | }, 38 | { 39 | "brand": "Chromium", 40 | "version": "108.0.5359.79" 41 | }, 42 | { 43 | "brand": "Google Chrome", 44 | "version": "108.0.5359.79" 45 | } 46 | ], 47 | "fullVersion": "108.0.5359.79", 48 | "platform": "Android", 49 | "platformVersion": "11.0.0", 50 | "architecture": "", 51 | "model": "HD1913", 52 | "mobile": true, 53 | "bitness": "", 54 | "wow64": false 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/selenium_profiles/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Profiles/90d6d4e668c506e1e2d90bdfafff45d9df71e735/src/selenium_profiles/scripts/__init__.py -------------------------------------------------------------------------------- /src/selenium_profiles/scripts/driver_utils.py: -------------------------------------------------------------------------------- 1 | class requests: 2 | def __init__(self, driver): 3 | self.driver = driver 4 | self.methods = ["GET","HEAD", "POST", "PUT", "DELETE","OPTIONS"] # TRACE, CONNECT exluded here 5 | self.supported_credentials = ["omit", "same-origin", "include"] 6 | self.modes = ["cors", "no-cors", "same-origin"] 7 | self.cache_values = ["default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached"] 8 | self.redirect_values = ["follow", "error"] # "manual" excluded here 9 | self.referrer_policies = ["no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url"] 10 | self.priorities = ["high", "low", "auto"] 11 | def fetch(self, url: str, 12 | method="GET", 13 | headers:dict=None, 14 | body:str or object=None, 15 | mode:str=None, 16 | credentials:str="same-origin", 17 | cache:str="no-cache", 18 | redirect="follow", 19 | referrer=None, 20 | referer_policy = None, 21 | priority="high" 22 | ): 23 | import json 24 | from selenium_profiles.utils.utils import read, check_cmd 25 | import codecs 26 | 27 | options = {} 28 | if method: 29 | check_cmd(method, self.methods) 30 | options["method"] = method 31 | if headers: 32 | options["headers"] = headers 33 | if body: 34 | if method in ["GET", "HEAD"]: 35 | raise ValueError("body can't be used with GET or HEAD method") 36 | options["body"] = body 37 | if mode: 38 | check_cmd(mode, self.modes) 39 | options["mode"] = mode 40 | if credentials: 41 | check_cmd(credentials, self.supported_credentials) 42 | options["credentials"] = credentials 43 | if cache: 44 | check_cmd(cache, self.cache_values) 45 | options["cache"] = cache 46 | if redirect: 47 | check_cmd(redirect, self.redirect_values) 48 | options["redirect"] = redirect 49 | if referrer: 50 | options["referrer"] = referrer 51 | if referer_policy: 52 | check_cmd(referer_policy,self.referrer_policies) 53 | options['referrerPolicy'] = referer_policy 54 | if priority: 55 | options["priority"] = priority 56 | 57 | 58 | 59 | options = json.dumps(options) 60 | url = json.dumps(url) 61 | js = read("js/fetch.js", sel_root=True) % (url, options) 62 | response = self.driver.execute_async_script(js) 63 | if response["status"] == "200": 64 | response = response["value"] 65 | response["content"] = codecs.decode(response["HEX"], "hex") 66 | response["text"] = response["content"].decode("utf-8", errors="replace") 67 | del response["HEX"] 68 | elif response["status"] == "error": 69 | raise Exception(response["stack"]) 70 | 71 | # https://developer.mozilla.org/en-US/docs/Web/API/fetch#syntax 72 | return response 73 | 74 | 75 | class actions(object): 76 | def __init__(self, driver): 77 | self._driver = driver 78 | self.TouchActionChain = TouchActionChain 79 | def sendkeys(self, keys): # send keys without specific Element 80 | from selenium.webdriver.common.action_chains import ActionChains 81 | action = ActionChains(self._driver) 82 | action.send_keys(str(keys)) 83 | action.perform() 84 | 85 | def mid_location(self, element): 86 | return {'x': element.location_once_scrolled_into_view['x'] + (element.rect["width"] / 2), 87 | 'y': element.location_once_scrolled_into_view['y'] + (element.rect["height"] / 2)} 88 | 89 | # Licensed to the Software Freedom Conservancy (SFC) under one 90 | # or more contributor license agreements. See the NOTICE file 91 | # distributed with this work for additional information 92 | # regarding copyright ownership. The SFC licenses this file 93 | # to you under the Apache License, Version 2.0 (the 94 | # "License"); you may not use this file except in compliance 95 | # with the License. You may obtain a copy of the License at 96 | # 97 | # http://www.apache.org/licenses/LICENSE-2.0 98 | # 99 | # Unless required by applicable law or agreed to in writing, 100 | # software distributed under the License is distributed on an 101 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 102 | # KIND, either express or implied. See the License for the 103 | # specific language governing permissions and limitations 104 | # under the License. 105 | 106 | # MODIFIED by Aurin Aegerter for Touch-actions instead of Mouse-actions 107 | 108 | """The TouchActionChains implementation,""" 109 | 110 | class TouchActionChain: 111 | 112 | def __init__(self, driver, duration=250): 113 | from selenium.webdriver.common.actions.action_builder import ActionBuilder 114 | from selenium.webdriver.common.actions import interaction 115 | from selenium.webdriver.common.actions.pointer_input import PointerInput 116 | """Creates a new ActionChains. 117 | 118 | :Args: 119 | - driver: The WebDriver instance which performs user actions. 120 | - duration: override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput 121 | """ 122 | self._driver = driver 123 | self.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"), 124 | duration=duration) 125 | 126 | def perform(self): 127 | """Performs all stored actions.""" 128 | self.w3c_actions.perform() 129 | 130 | def reset_actions(self): 131 | """Clears actions that are already stored locally and on the remote 132 | end.""" 133 | self.w3c_actions.clear_actions() 134 | for device in self.w3c_actions.devices: 135 | device.clear_actions() 136 | 137 | def tap(self, on_element=None): 138 | """Taps an element. 139 | 140 | :Args: 141 | - on_element: The element to tap on. 142 | If None, taps on current touch (pointer) position. 143 | """ 144 | if on_element: 145 | self.move_to_element(on_element) 146 | 147 | self.w3c_actions.pointer_action.click() 148 | self.w3c_actions.key_action.pause() 149 | self.w3c_actions.key_action.pause() 150 | 151 | return self 152 | 153 | def touch_and_hold(self, on_element=None): 154 | """Touch down on an element. 155 | 156 | :Args: 157 | - on_element: The element to touch down. 158 | If None, touches and holds on current touch (pointer) position. 159 | """ 160 | if on_element: 161 | self.move_to_element(on_element) 162 | 163 | self.w3c_actions.pointer_action.click_and_hold() 164 | self.w3c_actions.key_action.pause() 165 | 166 | return self 167 | 168 | def context_tap(self, on_element=None): 169 | """Performs a context-tap (right click) on an element. 170 | 171 | :Args: 172 | - on_element: The element to context-click. 173 | If None, taps on current touch (pointer) position. 174 | """ 175 | if on_element: 176 | self.move_to_element(on_element) 177 | 178 | self.w3c_actions.pointer_action.context_click() 179 | self.w3c_actions.key_action.pause() 180 | self.w3c_actions.key_action.pause() 181 | 182 | return self 183 | 184 | def double_tap(self, on_element=None): 185 | """Double-taps an element. 186 | 187 | :Args: 188 | - on_element: The element to double-tap. 189 | If None, taps on current touch (pointer) position. 190 | """ 191 | if on_element: 192 | self.move_to_element(on_element) 193 | 194 | self.w3c_actions.pointer_action.double_click() 195 | for _ in range(4): 196 | self.w3c_actions.key_action.pause() 197 | 198 | return self 199 | 200 | def drag_and_drop(self, source, target): 201 | """Touches down on the source element, then moves 202 | to the target element and releases. 203 | 204 | :Args: 205 | - source: The element to touch down. 206 | - target: The element to touch up. 207 | """ 208 | self.touch_and_hold(source) 209 | self.release(target) 210 | return self 211 | 212 | def drag_and_drop_by_offset(self, source, xoffset, yoffset): 213 | """Touches down on the source element, then moves 214 | to the target offset and releases. 215 | 216 | :Args: 217 | - source: The element to touch down. 218 | - xoffset: X offset to move to. 219 | - yoffset: Y offset to move to. 220 | """ 221 | self.touch_and_hold(source) 222 | self.move_by_offset(xoffset, yoffset) 223 | self.release() 224 | return self 225 | 226 | def move_by_offset(self, xoffset, yoffset): 227 | """Moving the touch (pointer) to an offset from current touch (pointer). 228 | 229 | :Args: 230 | - xoffset: X offset to move to, as a positive or negative integer. 231 | - yoffset: Y offset to move to, as a positive or negative integer. 232 | """ 233 | 234 | self.w3c_actions.pointer_action.move_by(xoffset, yoffset) 235 | self.w3c_actions.key_action.pause() 236 | 237 | return self 238 | 239 | def move_to_element(self, to_element): 240 | """Moving touch (pointer) to the middle of an element. 241 | 242 | :Args: 243 | - to_element: The WebElement to move to. 244 | """ 245 | 246 | self.w3c_actions.pointer_action.move_to(to_element) 247 | self.w3c_actions.key_action.pause() 248 | 249 | return self 250 | 251 | def move_to_element_with_offset(self, to_element, xoffset, yoffset): 252 | """Move touch (pointer) by an offset of the specified element. Offsets are 253 | relative to the in-view center point of the element. 254 | 255 | :Args: 256 | - to_element: The WebElement to move to. 257 | - xoffset: X offset to move to, as a positive or negative integer. 258 | - yoffset: Y offset to move to, as a positive or negative integer. 259 | """ 260 | 261 | self.w3c_actions.pointer_action.move_to(to_element, int(xoffset), int(yoffset)) 262 | self.w3c_actions.key_action.pause() 263 | 264 | return self 265 | 266 | def pause(self, seconds): 267 | """Pause all inputs for the specified duration in seconds.""" 268 | 269 | self.w3c_actions.pointer_action.pause(seconds) 270 | self.w3c_actions.key_action.pause(seconds) 271 | 272 | return self 273 | 274 | def release(self, on_element=None): 275 | """Releasing touch (pointer) on an element. 276 | 277 | :Args: 278 | - on_element: The element to touch up. 279 | If None, releases on current touch (pointer) position. 280 | """ 281 | if on_element: 282 | self.move_to_element(on_element) 283 | 284 | self.w3c_actions.pointer_action.release() 285 | self.w3c_actions.key_action.pause() 286 | 287 | return self 288 | 289 | # Context manager so ActionChains can be used in a 'with ... as' statements. 290 | 291 | def __enter__(self): 292 | return self # Return created instance of self. 293 | 294 | def __exit__(self, _type, _value, _traceback): 295 | pass # Do nothing, does not require additional cleanup. 296 | -------------------------------------------------------------------------------- /src/selenium_profiles/scripts/profiles.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from collections import defaultdict 3 | from typing import Dict 4 | 5 | from selenium_profiles.utils.utils import check_cmd, valid_key 6 | 7 | 8 | class cdp_handler: 9 | def __init__(self, driver): 10 | self._driver = driver 11 | 12 | self._supported_keys = ["touch", "maxtouchpoints", "emulation", "useragent", "patch_version", 13 | "cores", "darkmode","pinter_as_touch","cdp_args"] 14 | 15 | self.evaluate_on_document_identifiers = {} 16 | 17 | def apply(self, cdp_profile: bool or None = None): 18 | """ 19 | :param cdp_profile: => profile["cdp"] 20 | """ 21 | if cdp_profile: 22 | # noinspection PyShadowingNames 23 | profile = defaultdict(lambda: None) 24 | profile.update(cdp_profile) 25 | 26 | valid_key(cdp_profile.keys(), self._supported_keys, 'cdp_profile => profile["cdp"]') 27 | 28 | # is mobile ? 29 | try: 30 | # noinspection PyUnresolvedReferences 31 | mobile = profile["emulation"]["mobile"] 32 | except KeyError: 33 | mobile = False 34 | except TypeError: 35 | mobile = False 36 | 37 | # libs 38 | touchpoints = self.set_touchpoints(enabled=profile["touch"], maxpoints=profile["maxtouchpoints"]) 39 | emulation = self.set_emulation(profile["emulation"]) 40 | useragent = self.set_useragent(profile["useragent"], patch_version=profile["patch_version"]) 41 | cores = self.set_cores(cores_count=profile["cores"]) 42 | darkmode = self.darkmode(profile["darkmode"], mobile=mobile) 43 | pointer_as_touch = self.pointer_as_touch(profile["pinter_as_touch"], mobile=mobile) 44 | cdp_args = self.execute_cdp_args(profile["cdp_args"]) 45 | 46 | # execute list of cdp args 47 | 48 | 49 | return {"touchpoints": touchpoints, "emulation": emulation, "useragent": useragent, 50 | "cores": cores,"pointer_as_touch":pointer_as_touch,"darkmode":darkmode,"cdp_args": cdp_args} 51 | 52 | def execute_cdp_args(self, cdp_args:dict or None = None): 53 | """ 54 | :param cdp_args: dict of cdp_args => {"arg1":"value1", "arg2":"value2"} 55 | """ 56 | cdp_args_return = [] 57 | if not cdp_args: 58 | cdp_args = {} 59 | if cdp_args: 60 | if cdp_args[0]: 61 | for arg, value in cdp_args.items(): 62 | cdp_args_return.append(self._driver.execute_cdp_cmd(arg, value)) 63 | return cdp_args_return 64 | 65 | def set_useragent(self, useragent: dict or None = None, patch_version: str or bool = None): 66 | """ 67 | :param useragent: => https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride 68 | dict: 69 | { 70 | "userAgent":str, 71 | "acceptLanguage":optional-str, 72 | "platform":optional-str | => navigator.platform, 73 | "userAgentMetadata":optional-dict | => https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#type-UserAgentMetadata 74 | } 75 | :param patch_version: True=> automatically patch, str => patch with string 76 | """ 77 | if useragent: 78 | useragent = self.patch_version(useragent_profile=useragent, version=patch_version, driver=self._driver) 79 | return self._driver.execute_cdp_cmd('Emulation.setUserAgentOverride', useragent) 80 | 81 | def patch_version(self, useragent_profile: dict, version: str or bool or None = True, driver=None): 82 | """ 83 | :param useragent_profile: profile["cdp"]["useragent"] 84 | :param version: string or True (and driver specified => automatically get version) 85 | :param driver: driver object to automatically get the version 86 | :return: patched useragent_profile => profile["cdp"]["useragent"] 87 | """ 88 | profile = defaultdict(lambda: None) 89 | profile.update(useragent_profile) 90 | 91 | if version is True: 92 | if driver: 93 | useragent = driver.execute_cdp_cmd("Browser.getVersion",{})["product"] 94 | import re 95 | version = re.findall(r'(?<=Chrome/)\d+(?:\.\d+)+|(?<=Chromium/)\d+(?:\.\d+)+', useragent) 96 | version = version[0] 97 | else: 98 | raise ValueError("driver or version needs to be specified") 99 | 100 | if type(version) == str: 101 | import re 102 | if profile["userAgent"]: 103 | # noinspection PyTypeChecker 104 | profile["userAgent"] = re.sub(r"(?<=Chrome/)\d+(?:\.\d+)+|(?<=Chromium/)\d+(?:\.\d+)+", 105 | version.split(".")[0] + ".0.0.0", profile["userAgent"]) 106 | 107 | if profile["userAgentMetadata"]: 108 | metadata = defaultdict(lambda: None) 109 | # noinspection PyTypeChecker 110 | metadata.update(profile["userAgentMetadata"]) 111 | 112 | brands_pattern = r'^Not[ (:\-./);=?]A[ (:\-./);=?]Brand$' 113 | if metadata["brands"]: 114 | brands = [] 115 | # noinspection PyTypeChecker 116 | for brand in metadata["brands"]: 117 | if not re.match(brands_pattern, brand["brand"]): 118 | brand["version"] = version.split(".")[0] 119 | brands.append(brand) 120 | # noinspection PyTypeChecker 121 | metadata["brands"] = brands 122 | 123 | if metadata["fullVersionList"]: 124 | version_list = [] 125 | # noinspection PyTypeChecker 126 | for i in metadata["fullVersionList"]: 127 | if not re.match(brands_pattern, i["brand"]): 128 | i["version"] = version 129 | version_list.append(i) 130 | # noinspection PyTypeChecker 131 | metadata["fullVersionList"] = version_list 132 | 133 | if metadata["fullVersion"]: 134 | metadata["fullVersion"] = version 135 | 136 | # noinspection PyTypeChecker 137 | profile["userAgentMetadata"] = metadata 138 | 139 | return profile 140 | 141 | def set_emulation(self, emulation: dict or None = None): 142 | """ 143 | :param emulation: dict => https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride 144 | """ 145 | 146 | if emulation: 147 | 148 | # fix for https://github.com/kaliiiiiiiiii/Selenium-Profiles/issues/29 149 | if not "screenWidth" in emulation.keys(): 150 | emulation.update({"screenWidth": emulation["width"]}) 151 | if not "screenHeight" in emulation.keys(): 152 | emulation.update({"screenHeight": emulation["height"]}) 153 | self._driver.set_window_rect(0,0,emulation["width"], emulation["height"]) 154 | 155 | return self._driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', emulation) 156 | 157 | def clear_emulation(self, enabled:bool or None): 158 | if enabled or (enabled is None): 159 | self._driver.execute_cdp_cmd("Emulation.clearDeviceMetricsOverride", {}) 160 | 161 | def set_touchpoints(self, enabled: bool or None = None, maxpoints: int or None = None): 162 | """ 163 | :param enabled:bool 164 | :param maxpoints:int 165 | """ 166 | if not enabled is None: 167 | if maxpoints is None: 168 | maxpoints = 10 169 | return self._driver.execute_cdp_cmd('Emulation.setTouchEmulationEnabled', 170 | {'enabled': enabled, 'maxTouchPoints': maxpoints}) 171 | 172 | def set_cores(self, cores_count: int or None = 8): 173 | """ 174 | :param cores_count: int => navigator.hardwareConcurrency 175 | """ 176 | if cores_count: 177 | return self._driver.execute_cdp_cmd("Emulation.setHardwareConcurrencyOverride", 178 | {"hardwareConcurrency": cores_count}) 179 | 180 | 181 | def darkmode(self, enabled: bool or None = None, mobile: bool = False): 182 | if not enabled is None: 183 | if enabled: 184 | if not mobile: 185 | warnings.warn('darkmode might look weird without mobile_view!') 186 | return self._driver.execute_cdp_cmd('Emulation.setAutoDarkModeOverride', 187 | {'enabled': enabled}) 188 | 189 | def pointer_as_touch(self, enabled: None or bool = True, mobile: bool = None): 190 | if mobile or mobile is None: 191 | config = 'mobile' 192 | else: 193 | config = 'desktop' 194 | if not(enabled is None): 195 | warnings.warn('Actions execute, but then take long when "EmitTouchEventsForMouse"!') 196 | # executes, but then takes long [maybe check if success?] 197 | return self._driver.execute_cdp_cmd('Emulation.setEmitTouchEventsForMouse', {'enabled': enabled, 198 | 'configuration': config}) 199 | 200 | def evaluate_on_new_document(self, js: str): # evaluate js on every new page 201 | identifier = int( 202 | self._driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": js})["identifier"]) 203 | self.evaluate_on_document_identifiers.update({identifier: js}) 204 | return identifier 205 | 206 | # remove js evaluation on every new page for specific script 207 | def remove_evaluate_on_document(self, identifier: int): 208 | del self.evaluate_on_document_identifiers[identifier] 209 | return self._driver.execute_cdp_cmd("Page.removeScriptToEvaluateOnNewDocument", {"identifier": str(identifier)}) 210 | 211 | class options: # webdriver.Chrome or uc.Chrome options 212 | # noinspection PyDefaultArgument,PyShadowingNames 213 | def __init__(self, options, options_profile: dict or None = None, duplicate_policy: str = "warn-add", 214 | safe_duplicates: list = None): 215 | """ 216 | :param options: for example ChromeOptions() 217 | :param options_profile: # profile["options"] for a Selenium-Profiles profile 218 | :param duplicate_policy: for args | "raise" or "replace" or "warn-replace" or "skip" or "warn-skip" or "add" or "warn-add" 219 | exact duplicates always get removed with a warning 220 | """ 221 | if not options_profile: 222 | options_profile = {} 223 | self.profile = defaultdict(lambda: None) 224 | self.profile.update(options_profile) 225 | self._extensions = [] 226 | 227 | self.duplicate_policy = duplicate_policy 228 | self.duplicates = defaultdict(lambda: set()) 229 | if not safe_duplicates: 230 | safe_duplicates = [] 231 | self.safe_duplicates = safe_duplicates 232 | 233 | self.Options = options 234 | self.to_capabilities = self.Options.to_capabilities 235 | 236 | self._profile_keys = ["sandbox", "headless", "load_images", "incognito", "touch", "app", "gpu", 237 | "proxy", "args", "capabilities", "experimental_options", "adb", "adb_package", 238 | "use_running_app", 239 | "extension_paths", 240 | ] 241 | self._supported_duplicate_policies = ["raise", "replace", "warn-replace", 242 | "skip", "warn-skip", "add", "warn-add"] 243 | 244 | self.apply(options_profile) 245 | 246 | def apply(self, options_profile): 247 | profile = defaultdict(lambda: None) 248 | profile.update(options_profile) 249 | 250 | valid_key(profile.keys(), self._profile_keys, "options_profile => profile['options']") 251 | # libs 252 | self.sandbox(enabled=profile["sandbox"], adb=profile["adb"]) 253 | self.headless(profile["headless"], profile["load_images"]) 254 | self.incognito(profile["incognito"], profile["extensions"]) 255 | self.touch(profile["touch"]) 256 | self.app(profile["app"], adb=profile["adb"]) 257 | self.gpu(profile["gpu"], adb=profile["adb"]) 258 | self.proxy(profile["proxy"]) 259 | self.extend_arguments(profile["args"]) 260 | self.update_capabilities(profile["capabilities"]) 261 | self.update_experimental_options(profile["experimental_options"]) 262 | self.adb_remote(profile["adb"], package=profile["adb_package"], use_running_app=profile["use_running_app"]) 263 | self.add_extensions(profile["extension_paths"], adb=profile["adb"]) 264 | return self.Options 265 | 266 | # noinspection PyIncorrectDocstring 267 | def sandbox(self, enabled: bool or None = None, adb: bool or None = None): 268 | """ 269 | :param enabled: defaults to True 270 | """ 271 | if enabled is False: 272 | self.warn_adb_unsupported(adb, "disabling sandbox") 273 | self.extend_arguments(["--no-sandbox", "--test-type"]) 274 | 275 | # noinspection PyIncorrectDocstring 276 | def headless(self, headless: bool or None = None, load_images: bool or None = None, adb: bool or None = None): 277 | """ 278 | :param headless: defaults to False 279 | :param load_images: defaults to True 280 | """ 281 | 282 | if headless: 283 | self.warn_adb_unsupported(adb, "enabling headless") 284 | self.add_argument('--headless=new') 285 | if load_images is False: 286 | self.warn_adb_unsupported(adb, "disabling images") 287 | self.add_argument("--blink-settings=imagesEnabled=false") 288 | 289 | # noinspection PyIncorrectDocstring 290 | def incognito(self, enabled: bool or None = False, extension_used: bool or None = None, adb: bool or None = None): 291 | """ 292 | :param enabled: incognito defaults to False 293 | :param extension_used: defaults to False 294 | 295 | prints warning if extensions are used 296 | """ 297 | if enabled: 298 | self.warn_adb_unsupported(adb, "enabling incognito") 299 | if extension_used: 300 | warnings.warn('Incognito is rarely working with Extensions.') 301 | self.add_argument("--incognito") 302 | 303 | def touch(self, enabled: bool or None = None): 304 | """ 305 | :param enabled: defaults to default 306 | """ 307 | 308 | if enabled is False: 309 | self.add_argument("--touch-events=disabled") 310 | elif enabled is True: 311 | self.add_argument("--touch-events=enabled") 312 | 313 | # noinspection PyIncorrectDocstring 314 | def app(self, enabled: bool or None = None, adb: bool or None = None): 315 | """ 316 | :param enabled: defaults to False 317 | 318 | disables some Desktop window features 319 | """ 320 | 321 | if enabled is True: 322 | self.warn_adb_unsupported(adb, "enabling app view") 323 | self.add_argument('--app') 324 | self.add_argument('--force-app-mode') 325 | 326 | def gpu(self, enabled: bool or None = None, adb: bool or None = None): 327 | """ 328 | disables GPU for Chrome 329 | :param enabled: defaults to True 330 | :param adb: defaults to False 331 | 332 | Not supported on Android Hardware 333 | """ 334 | 335 | if enabled is False: 336 | if not adb: 337 | self.add_argument('--disable-gpu') 338 | self.add_argument('--override-use-software-gl-for-tests') 339 | else: 340 | warnings.warn('Disabling GPU not supported when running on Android hardware, skipping') 341 | 342 | def adb_remote(self, enabled: bool or None = None, package: str or None = None, 343 | use_running_app: bool = None): 344 | """ 345 | :param enabled: defaults to False 346 | :param package: defaults to 'com.android.chrome' 347 | :param use_running_app: defaults to True 348 | """ 349 | 350 | if package is None: # default 351 | package = 'com.android.chrome' 352 | if enabled: 353 | self.update_experimental_options({'androidPackage': package}) 354 | if use_running_app or use_running_app is None: 355 | self.update_experimental_options({'androidUseRunningApp': True}) 356 | 357 | def warn_adb_unsupported(self, adb: bool or None, method: str): 358 | if adb: 359 | warnings.warn(f"specifying {method} not supported on Android hardware") 360 | 361 | def proxy(self, proxy: str = None): 362 | """ 363 | :param proxy: scheme://host:port => https://example.com:9000 364 | """ 365 | supported_schemes = ["http", "https", "socks4", "socks5"] 366 | 367 | if proxy: 368 | scheme = proxy.split("://")[0] 369 | check_cmd(scheme, supported_schemes) 370 | if "@" in proxy: 371 | raise ValueError("Proxies specified in options don't allow authentification") 372 | self.add_argument('--proxy-server=' + proxy) 373 | 374 | ## tools ## 375 | 376 | def extend_arguments(self, my_args: list = None, duplicate_policy=None): 377 | 378 | if my_args: 379 | for arg in my_args: 380 | self.add_argument(arg, duplicate_policy=duplicate_policy) 381 | 382 | def add_argument(self, my_option: str, duplicate_policy=None): 383 | """ 384 | :param my_option: argument to add 385 | :param duplicate_policy: "raise" or "replace" or "warn-replace" or "skip" or "warn-skip" or "add" or "warn-add" 386 | """ 387 | 388 | if duplicate_policy: 389 | policy = duplicate_policy 390 | else: 391 | policy = self.duplicate_policy 392 | 393 | check_cmd(policy, self._supported_duplicate_policies) 394 | my_arg = my_option.split("=")[0] 395 | duplicates_found = False 396 | # iterateover current options 397 | if self.Options.arguments: 398 | for i, option in list(enumerate(self.Options.arguments)): 399 | arg = option.split("=")[0] 400 | 401 | if my_arg == arg: # got duplicate 402 | if my_option == option: 403 | warnings.warn(f"exact duplicate found for {my_option}, skipping") 404 | return 405 | else: 406 | duplicates_found = True 407 | if my_arg not in self.safe_duplicates: 408 | self.duplicates[arg].update({my_option, option}) 409 | # replace 410 | if policy == "replace": 411 | self.Options.arguments[i] = my_option 412 | elif policy == "warn-replace": 413 | self.Options.arguments[i] = my_option 414 | warnings.warn(f"found duplicate for {my_option}: {option} , replacing") 415 | # skipp 416 | elif policy == "skip": 417 | pass 418 | elif policy == "warn-skip": 419 | warnings.warn(f"found duplicate for {my_option}: {option}, skipping") 420 | 421 | # raise 422 | elif policy == "raise": 423 | raise ValueError(f"found duplicate for {my_option}: {option}") 424 | else: 425 | self.Options.arguments.append(my_option) 426 | return 427 | 428 | # add 429 | if duplicates_found: # we only want to add them once:) 430 | if my_arg in self.safe_duplicates: 431 | self.Options.arguments.append(my_option) 432 | return 433 | if policy == "add": 434 | self.Options.arguments.append(my_option) 435 | elif policy == "warn-add": 436 | self.Options.arguments.append(my_option) 437 | warnings.warn(f"found duplicates for {my_option}: {self.duplicates[my_arg]} , adding") 438 | 439 | else: # first option to add 440 | self.Options.arguments.append(my_option) 441 | 442 | def update_capabilities(self, capabilities: dict or None = None, duplicate_policy=None): 443 | """ 444 | :param duplicate_policy: self._supported_duplicate_policies 445 | :param capabilities: dict of {"capability":value} 446 | 447 | handling of duplicates not implemented yet! 448 | """ 449 | if duplicate_policy: 450 | policy = duplicate_policy 451 | else: 452 | policy = self.duplicate_policy 453 | 454 | if capabilities: 455 | check_cmd(policy, self._supported_duplicate_policies) 456 | for cap, value in capabilities.items(): 457 | if cap in self.Options.capabilities.keys(): 458 | duplicate = self.Options.capabilities[cap] 459 | if policy == "replace": 460 | self.Options.set_capability(cap, value=value) 461 | elif policy == "warn-replace": 462 | self.Options.set_capability(cap, value=value) 463 | warnings.warn(f"got duplicate for {cap}: {duplicate} , replacing") 464 | # skipp 465 | elif policy == "skip": 466 | pass 467 | elif policy == "warn-skip": 468 | warnings.warn(f"got duplicate for {cap}: {duplicate}, skipping") 469 | elif policy == "add" or policy == "warn-add": 470 | warnings.warn( 471 | f"got duplicate for {cap}: {duplicate} ,policy is {policy}, but capabilties need to be unique, skipping") 472 | # raise 473 | elif policy == "raise": 474 | raise ValueError(f"got duplicate for {cap}: {duplicate}") 475 | else: 476 | self.Options.set_capability(cap, value=value) 477 | 478 | def update_experimental_options(self, experimental_options: dict or None = None, duplicate_policy=None): 479 | """ 480 | :param experimental_options: dict of {"experimental_option":value} 481 | :param duplicate_policy: self._supported_duplicate_policies 482 | """ 483 | if duplicate_policy: 484 | policy = duplicate_policy 485 | else: 486 | policy = self.duplicate_policy 487 | 488 | if experimental_options: 489 | check_cmd(policy, self._supported_duplicate_policies) 490 | for key, value in experimental_options.items(): 491 | if key in self.Options.experimental_options.keys(): 492 | duplicate = self.Options.experimental_options[key] 493 | if policy == "replace": 494 | self.Options.add_experimental_option(key, value=value) 495 | elif policy == "warn-replace": 496 | self.Options.add_experimental_option(key, value=value) 497 | warnings.warn(f"got duplicate for {key}: {duplicate} , replacing") 498 | # skipp 499 | elif policy == "skip": 500 | pass 501 | elif policy == "warn-skip": 502 | warnings.warn(f"got duplicate for {key}: {duplicate}, skipping") 503 | elif policy == "add" or policy == "warn-add": 504 | warnings.warn( 505 | f"got duplicate for {key}: {duplicate} ,policy is {policy}, but experimental_options need to be unique, skipping") 506 | # raise 507 | elif policy == "raise": 508 | raise ValueError(f"got duplicate for {key}: {duplicate}") 509 | else: 510 | self.Options.add_experimental_option(key, value=value) 511 | 512 | # extensions 513 | def add_extensions(self, extension_paths: None or list = None, adb: bool or None = None): 514 | """ 515 | :param adb: adding extensions not supported when running on Android hardware 516 | :param extension_paths: list of paths to add extension from 517 | """ 518 | 519 | import os 520 | if extension_paths: 521 | if adb: 522 | raise ValueError("adding extensions not supported when running on Android hardware") 523 | for extension_path in extension_paths: 524 | file_type = extension_path[-4:] 525 | if os.path.exists(extension_path): 526 | if os.path.isfile(extension_path): 527 | if not (file_type == ".crx" or file_type == ".zip"): 528 | warnings.warn("Extension-file isn't *.zip or *.crx") 529 | self.Options.add_extension(extension_path) 530 | elif os.path.isdir(extension_path): 531 | self._extensions.append(extension_path) 532 | else: 533 | raise LookupError("Extension-path doesn't exist") 534 | -------------------------------------------------------------------------------- /src/selenium_profiles/scripts/proxy.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class DynamicProxy: 4 | def __init__(self, driver, injector=None): 5 | self._injector = injector 6 | self._driver = driver 7 | self._seleniumwire = None 8 | 9 | if not self._injector: 10 | if "profiles" in driver.__dir__(): 11 | if "injector" in driver.__dir__(): 12 | self._injector = self._driver.profiles.injector 13 | if "proxy" in self._driver.__dir__(): 14 | self._seleniumwire = True 15 | if not(self._seleniumwire or self._injector): 16 | raise ModuleNotFoundError("either seleniumwire or selenium-injecter is needed for dynamic proxies") 17 | 18 | def str2val(self, url): 19 | from urllib.parse import unquote 20 | creds = "" 21 | try: 22 | scheme, url = url.split("://") 23 | if "@" in url: 24 | creds, url = url.split("@") 25 | host, port = url.split(":") 26 | port = int(port) 27 | if not (scheme and host and port): 28 | raise ValueError 29 | values = {"host": host, "port": port, "scheme": scheme} 30 | if creds: 31 | username, password = creds.split(":") 32 | username, password = unquote(username), unquote(password) 33 | if not (username and password): 34 | raise ValueError 35 | creds = {"password": password, "username": username} 36 | values.update(creds) 37 | except ValueError: 38 | raise ValueError("expected proxy_url, but got " + url) 39 | return values 40 | 41 | def val2str(self, proxy_dict: dict or None, creds: dict = None): 42 | from urllib.parse import quote 43 | if proxy_dict: 44 | defdict = defaultdict(lambda: "") 45 | defdict.update(proxy_dict) 46 | if creds: 47 | # noinspection PyTypeChecker 48 | creds = quote(creds["username"]) + ':' + quote(creds["password"]) + '@' 49 | else: 50 | creds = "" 51 | parsed = defdict["scheme"] + "://" + creds + defdict["host"] + ":" + str(defdict["port"]) 52 | return parsed 53 | 54 | # noinspection PyDefaultArgument 55 | def set_single(self, proxy, bypass_list:list or None = ["localhost", "127.0.0.1"]): 56 | if not bypass_list: 57 | bypass_list = ["localhost", "127.0.0.1"] 58 | if self._seleniumwire: 59 | self._driver.proxy = {"http":proxy, "https":proxy, "no_proxy":",".join(bypass_list)} 60 | elif self._injector: 61 | proxy = self.str2val(proxy) 62 | proxy.update({"bypass_list":bypass_list}) 63 | self._injector.proxy.set_single(**proxy) 64 | 65 | @property 66 | def proxy(self): 67 | 68 | if self._seleniumwire: 69 | proxies = defaultdict(lambda: None) 70 | proxies.update(self._driver.proxy) 71 | if proxies["no_proxy"]: 72 | # noinspection PyUnresolvedReferences 73 | proxies["bypass_list"] = proxies["no_proxy"].split(",") 74 | del proxies["no_proxy"] 75 | if not proxies["http"] or proxies["https"]: 76 | # noinspection PyTypeChecker 77 | proxies["mode"] = "system" 78 | return dict(proxies) 79 | 80 | elif self._injector: 81 | proxy = self._injector.proxy 82 | mode = proxy.mode 83 | auth = proxy.auth 84 | if "urls" in auth.keys(): 85 | del auth["urls"] 86 | if not auth: 87 | auth = None 88 | 89 | if mode == "fixed_servers": 90 | rules = defaultdict(lambda: None) 91 | rules.update(proxy.rules) 92 | bypass_list = rules["bypassList"] 93 | if not bypass_list: 94 | bypass_list = [] 95 | if rules["singleProxy"]: 96 | # noinspection PyTypeChecker 97 | single = self.val2str(rules["singleProxy"], creds=auth) 98 | return {"http": single, "https": single, "ftp": single, 99 | "mode": mode, "bypass_list": bypass_list,"single_proxy":True, "auth": auth} 100 | else: 101 | return {"http": self.val2str(rules["proxyForHttp"], creds=auth), 102 | "https": self.val2str(rules["proxyForHttps"], creds=auth), 103 | "ftp": self.val2str(rules["proxyForFtp"], creds=auth), 104 | "fallback_proxy": self.val2str(rules["fallbackProxy"], creds=auth), 105 | "mode": mode, "bypass_list": bypass_list,"single_proxy":False, "auth": auth} 106 | else: 107 | return {"mode": mode, "rules": proxy.rules} 108 | else: 109 | return -------------------------------------------------------------------------------- /src/selenium_profiles/scripts/undetected.py: -------------------------------------------------------------------------------- 1 | from selenium_profiles.utils.utils import read 2 | 3 | 4 | def exec_cdp(driver, cdp_handler = None): 5 | if not cdp_handler: 6 | if "profiles" in driver.__dir__(): 7 | cdp_handler = driver.profiles.cdp_handler 8 | else: 9 | from selenium_profiles.scripts.profiles import cdp_handler 10 | cdp_handler = cdp_handler(driver=driver) 11 | 12 | # set webdriver js var to false 13 | cdp_handler.evaluate_on_new_document(read('js/undetected/navigator_webdriver.js')) 14 | 15 | driver.execute_cdp_cmd('Emulation.setIdleOverride', {'isUserActive': True, 'isScreenUnlocked': True}) 16 | 17 | cdp_handler.remove_evaluate_on_document(1) 18 | 19 | 20 | def config_options(options, adb=False, duplicate_policy="warn-replace"): 21 | from selenium_profiles.scripts.profiles import options as option_manager 22 | manager = option_manager(options) 23 | if "excludeSwitches" in options.experimental_options.keys(): 24 | if "enable-automation" not in options.experimental_options["excludeSwitches"]: 25 | manager.Options.experimental_options["excludeSwitches"].append("enable-automation") 26 | else: 27 | manager.update_experimental_options({"excludeSwitches": ["enable-automation"]}, duplicate_policy=duplicate_policy) 28 | if not adb or adb is None: 29 | manager.update_experimental_options({'useAutomationExtension': False}, duplicate_policy=duplicate_policy) 30 | manager.extend_arguments(["--disable-blink-features=AutomationControlled"], duplicate_policy="warn-add") 31 | 32 | # suppress welcome 33 | manager.extend_arguments(["--no-default-browser-check", "--no-first-run"], duplicate_policy="warn-add") 34 | 35 | return manager.Options 36 | -------------------------------------------------------------------------------- /src/selenium_profiles/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Profiles/90d6d4e668c506e1e2d90bdfafff45d9df71e735/src/selenium_profiles/utils/__init__.py -------------------------------------------------------------------------------- /src/selenium_profiles/utils/colab_utils.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | 4 | def restart_runtime(): 5 | warnings.warn('Restarting runtime..') 6 | if is_colab(): 7 | import os 8 | os.kill(os.getpid(), 9) 9 | else: 10 | raise EnvironmentError('Restarting environment necessary..!') 11 | 12 | 13 | def is_colab(): 14 | import sys 15 | # noinspection PyBroadException 16 | try: 17 | # noinspection PyPackageRequirements 18 | import google.colab 19 | cimport = True 20 | except: 21 | cimport = False 22 | if ('google.colab' in sys.modules) and cimport: 23 | return True 24 | else: 25 | return False 26 | 27 | 28 | def is_jupyter() -> bool: 29 | """It checks whether a module is run from a Jupyter notebook""" 30 | try: 31 | # noinspection PyUnresolvedReferences,PyStatementEffect 32 | get_ipython 33 | return True 34 | except NameError: 35 | # noinspection PyTypeChecker 36 | return 37 | 38 | # noinspection PyPep8Naming 39 | class display: 40 | def __init__(self): 41 | self.display = None 42 | 43 | def start_display(self, x=1200, y=2200): 44 | # noinspection PyUnresolvedReferences,PyPackageRequirements 45 | from pyvirtualdisplay import Display 46 | self.display = Display(visible=0, size=(x, y)) 47 | self.display.start() 48 | 49 | def stop_display(self): 50 | self.display.stop() 51 | 52 | 53 | # noinspection PyUnresolvedReferences,PyPackageRequirements 54 | def showscreen(driver): 55 | from IPython.display import display as imagerender 56 | from IPython.display import Image 57 | driver.save_screenshot('screen.png') 58 | imagerender(Image('screen.png')) 59 | 60 | 61 | def show_html(driver): 62 | # noinspection PyUnresolvedReferences,PyPackageRequirements 63 | from IPython.display import display, HTML, IFrame 64 | display(HTML(str(driver.page_source))) 65 | 66 | 67 | def exec_js(js: str): 68 | # noinspection PyUnresolvedReferences,PyPackageRequirements 69 | from IPython.display import Javascript 70 | Javascript(js) 71 | -------------------------------------------------------------------------------- /src/selenium_profiles/utils/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import selenium_profiles 5 | from selenium_profiles.utils.colab_utils import is_colab 6 | 7 | 8 | def sel_profiles_path(): 9 | return os.path.dirname(selenium_profiles.__file__) + "/" 10 | 11 | 12 | def read(filename: str, encoding: str = "utf-8", sel_root=True): 13 | if sel_root: 14 | path = sel_profiles_path() + filename 15 | else: 16 | path = filename 17 | with open(path, encoding=encoding) as f: 18 | return f.read() 19 | 20 | def write(filename: str, content:str, encoding: str = "utf-8", sel_root=True): 21 | if sel_root: 22 | path = sel_profiles_path() + filename 23 | else: 24 | path = filename 25 | with open(path,"w", encoding=encoding) as f: 26 | return f.write(content) 27 | 28 | def read_json(filename: str = 'example.json', encoding: str = "utf-8",sel_root=True): 29 | if sel_root: 30 | path = sel_profiles_path() + filename 31 | else: 32 | path = filename 33 | with open(path, 'r', encoding=encoding) as f: 34 | return json.load(f) 35 | 36 | 37 | def write_json(obj: dict or list, filename: str = "out.json", encoding: str = "utf-8",sel_root=True): 38 | if sel_root: 39 | path = sel_profiles_path() + filename 40 | else: 41 | path = filename 42 | with open(path, "w", encoding=encoding) as outfile: 43 | outfile.write(json.dumps(obj)) 44 | 45 | def check_cmd(value, values): 46 | if value not in values: 47 | raise ValueError("got " + str(value) + " , but expected " + str(values)) 48 | 49 | def valid_key(got:list or set, valid:list, obj_name:str): 50 | for key in got: 51 | if key not in valid: 52 | raise ValueError(f"'{key}' isn't a valid key for {obj_name}") 53 | 54 | def my_platform(): 55 | import platform 56 | if is_colab(): 57 | return "Google-Colab" 58 | else: 59 | return platform.system() 60 | -------------------------------------------------------------------------------- /src/selenium_profiles/webdriver.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from collections import defaultdict 3 | import typing 4 | 5 | from selenium.webdriver.chrome.service import Service as ChromeService 6 | 7 | from selenium_profiles.utils.colab_utils import is_colab 8 | from selenium_profiles.scripts.profiles import options as options_handler 9 | 10 | from selenium.webdriver import Chrome as BaseDriver 11 | class Chrome(BaseDriver): 12 | 13 | # noinspection PyDefaultArgument 14 | def __init__(self, profile: dict = None, chrome_binary: str = None, executable_path: str = None, 15 | options=None, duplicate_policy: str = "warn-add", safe_duplicates: list = list(), 16 | base_drivers:tuple=None, 17 | uc_driver: bool or None = None, seleniumwire_options: dict or bool or None = None, 18 | injector_options:dict or bool or None = None, driverless_options = None, 19 | **kwargs): 20 | 21 | 22 | import undetected_chromedriver as uc_webdriver 23 | import seleniumwire.undetected_chromedriver as wire_uc_webdriver 24 | from seleniumwire import webdriver as wire_webdriver 25 | 26 | 27 | from selenium_profiles.utils.utils import valid_key 28 | 29 | if not base_drivers: 30 | base_drivers = tuple() 31 | 32 | # import webdriver 33 | webdriver = None 34 | if uc_driver: 35 | if seleniumwire_options: 36 | webdriver = wire_uc_webdriver 37 | else: 38 | webdriver = uc_webdriver 39 | else: 40 | if seleniumwire_options: 41 | webdriver = wire_webdriver 42 | 43 | if webdriver: 44 | base_drivers = (webdriver.Chrome,) + base_drivers 45 | 46 | if not options: 47 | if webdriver: 48 | options = webdriver.ChromeOptions() 49 | else: 50 | from selenium.webdriver import ChromeOptions 51 | options = ChromeOptions() 52 | 53 | if driverless_options: 54 | try: 55 | # noinspection PyUnresolvedReferences 56 | from selenium_driverless.sync.webdriver import Chrome as DriverlessChrome 57 | except: 58 | raise RuntimeError("selenium-driverless requires Python>=3.8") 59 | base_drivers = base_drivers + (DriverlessChrome,) 60 | 61 | if len(base_drivers) > 1: 62 | warnings.warn("More than one base_driver might not initialize correctly, seems buggy.\n Also, you might try different order") 63 | if (len(base_drivers) == 1) and (base_drivers[0] == Chrome.__base__): 64 | pass # got selenium.webdriver.Chrome as BaseDriver 65 | elif not base_drivers: 66 | pass 67 | else : 68 | Chrome.__bases__ = base_drivers 69 | 70 | if not profile: 71 | profile = {} 72 | 73 | valid_key(profile.keys(), ["cdp", "options", "proxy"], "profile (selenium-profiles)") 74 | 75 | if type(seleniumwire_options) is dict: 76 | kwargs.update({"seleniumwire_options": seleniumwire_options}) 77 | elif not (type(seleniumwire_options) is bool or seleniumwire_options is None): 78 | raise ValueError("Expected NoneType, dict or bool") 79 | 80 | defdict = defaultdict(lambda: None) 81 | defdict.update(profile) 82 | profile = defdict 83 | proxy = defaultdict(lambda :None) 84 | # noinspection PyTypeChecker 85 | if profile["proxy"]: 86 | # noinspection PyTypeChecker 87 | proxy.update(profile["proxy"]) 88 | 89 | if proxy["proxy"] and (not seleniumwire_options): 90 | injector_options = True 91 | 92 | # sandbox handling for google-colab 93 | if is_colab(): 94 | # todo: nested default-dict with Lambda: None 95 | if profile["options"]: 96 | # noinspection PyUnresolvedReferences 97 | if 'sandbox' in profile["options"].keys(): 98 | # noinspection PyUnresolvedReferences 99 | if profile["options"]["sandbox"] is True: 100 | warnings.warn('Google-colab doesn\'t work with sandbox enabled yet, disabling sandbox') 101 | else: 102 | # noinspection PyUnresolvedReferences 103 | profile["options"].update({"sandbox": False}) 104 | else: 105 | # noinspection PyTypeChecker 106 | profile.update({"options": {"sandbox": False}}) 107 | 108 | # options-manager 109 | options_manager = options_handler(options, profile["options"], duplicate_policy=duplicate_policy, 110 | safe_duplicates=safe_duplicates) 111 | 112 | # chrome executable path 113 | if chrome_binary: 114 | options_manager.Options.binary_location = chrome_binary 115 | 116 | if (uc_webdriver.Chrome in base_drivers) or (wire_uc_webdriver.Chrome in base_drivers): 117 | if executable_path: 118 | kwargs.update({"driver_executable_path": executable_path}) 119 | elif driverless_options: 120 | pass 121 | else: 122 | # detectability options 123 | from selenium_profiles.scripts import undetected 124 | 125 | # is adb used ? 126 | try: 127 | # noinspection PyUnresolvedReferences 128 | adb = profile["options"]["adb"] 129 | except TypeError: 130 | adb = None 131 | except KeyError: 132 | adb = None 133 | 134 | options_manager.Options = undetected.config_options(options_manager.Options, adb=adb) 135 | 136 | # chromedriver path 137 | if executable_path: 138 | kwargs.update({"service": ChromeService(executable_path=executable_path)}) 139 | 140 | injector = None 141 | if injector_options: 142 | from selenium_injector.scripts.injector import Injector 143 | if (injector_options is True) or injector_options == {}: 144 | injector_options = {} 145 | injector = Injector(**injector_options) 146 | options_manager.add_extensions(injector.paths) 147 | 148 | 149 | # add options to kwargs 150 | # noinspection PyProtectedMember,PyUnresolvedReferences 151 | options_manager.add_argument(f'--load-extension={",".join(options_manager._extensions)}') 152 | kwargs.update({"options": options_manager.Options}) 153 | 154 | # Actual start of chrome 155 | super().__init__(**kwargs) 156 | # cdp tools 157 | 158 | self.get("chrome://version/") # wait browser to start 159 | 160 | self.profiles = profiles(self, profile, selenium_injector=injector) 161 | 162 | 163 | self.profiles.cdp_handler.evaluate_on_document_identifiers.update({1: # we know that it is there:) 164 | """(function () {window.cdc_adoQpoasnfa76pfcZLmcfl_Array = window.Array; 165 | window.cdc_adoQpoasnfa76pfcZLmcfl_Object = window.Object; 166 | window.cdc_adoQpoasnfa76pfcZLmcfl_Promise = window.Promise; 167 | window.cdc_adoQpoasnfa76pfcZLmcfl_Proxy = window.Proxy; 168 | window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol = window.Symbol; 169 | }) ();"""}) 170 | 171 | self.profiles.cdp_handler.apply(cdp_profile=profile["cdp"]) 172 | 173 | if not (uc_driver or driverless_options): 174 | from selenium_profiles.scripts import undetected 175 | undetected.exec_cdp(self, cdp_handler=self.profiles.cdp_handler) 176 | 177 | if proxy["proxy"]: 178 | from selenium_profiles.utils.utils import valid_key 179 | # noinspection PyUnresolvedReferences 180 | valid_key(proxy.keys(), ["proxy", "bypass_list"], '"profiles["proxy"]"') 181 | # noinspection PyUnresolvedReferences 182 | self.profiles.proxy.set_single(proxy["proxy"], bypass_list=proxy["bypass_list"]) 183 | 184 | def get_cookies(self, urls:typing.List[str] = None) -> typing.List[dict]: 185 | arg = {} 186 | if urls: 187 | arg["urls"] = urls 188 | return self.execute_cdp_cmd("Network.getAllCookies", arg)['cookies'] 189 | 190 | def add_cookie(self, cookie_dict:dict) -> None: 191 | self.execute_cdp_cmd("Network.setCookie",cookie_dict) 192 | 193 | def delete_cookie(self, name:str, url:str=None, domain:str=None, path:str=None) -> None: 194 | arg = {"name": name} 195 | if url: 196 | arg["url"] = url 197 | if domain: 198 | arg["domain"] = domain 199 | if path: 200 | arg["path"] = path 201 | self.execute_cdp_cmd("Network.deleteCookies", arg) 202 | 203 | def delete_all_cookies(self) -> None: 204 | self.execute_cdp_cmd("Network.clearBrowserCookies", {}) 205 | 206 | def quit(self) -> None: 207 | if "profiles" in self.__dir__(): 208 | if "driverless" in self.profiles.__dir__(): 209 | # noinspection PyUnresolvedReferences 210 | self.profiles.driverless.stop() 211 | super().quit() 212 | 213 | class profiles: 214 | # noinspection PyShadowingNames 215 | def __init__(self, driver, profile, cdp_handler=None, selenium_injector=None): 216 | 217 | from selenium_interceptor.interceptor import cdp_listener 218 | from selenium_profiles.scripts.driver_utils import requests, actions 219 | from selenium_profiles.scripts.proxy import DynamicProxy 220 | 221 | self._driver = driver 222 | self._profile = profile 223 | self.injector = selenium_injector 224 | 225 | if cdp_handler: 226 | self.cdp_handler = cdp_handler 227 | elif "profiles" in driver.__dir__(): 228 | self.cdp_handler = driver.profiles.cdp_handler 229 | else: 230 | from selenium_profiles.scripts.profiles import cdp_handler 231 | self.cdp_handler = cdp_handler(self._driver) 232 | 233 | self.cdp_listener = cdp_listener(driver=self._driver) 234 | self.actions = actions(self._driver) 235 | 236 | requests = requests(self._driver) 237 | self.fetch = requests.fetch 238 | try: 239 | self.proxy = DynamicProxy(self._driver, injector=self.injector) 240 | except ModuleNotFoundError: 241 | pass # not supported 242 | 243 | 244 | # noinspection PyShadowingNames 245 | def apply(self, profile: dict): 246 | """ 247 | apply options after driver already started 248 | :param profile: selenium-profiles options 249 | """ 250 | from selenium_profiles.utils.utils import valid_key 251 | valid_key(profile.keys(), ["cdp", "options"], "profile (selenium-profiles)") 252 | if "options" in profile.keys(): 253 | warnings.warn('profile["options"] can\'t be applied when driver already started') 254 | if "cdp" in profile.keys(): 255 | # noinspection PyUnresolvedReferences 256 | self.cdp_handler.apply(profile["cdp"]) 257 | 258 | def get_profile(self): 259 | from selenium_profiles.utils.utils import read 260 | js = read('js/export_profile.js', sel_root=True) 261 | return self._driver.execute_async_script(js) 262 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /tests/selenium_detector.py: -------------------------------------------------------------------------------- 1 | from selenium_profiles.webdriver import Chrome 2 | from selenium_profiles.profiles import profiles 3 | from selenium.webdriver.common.by import By 4 | 5 | # Start Driver 6 | 7 | profile = profiles.Windows() # or .Android 8 | driver = Chrome(profile, uc_driver=False) 9 | 10 | def click(x,y): 11 | driver.execute_cdp_cmd("Input.dispatchMouseEvent",{"type":"mousePressed", "x":x, "y":y}) 12 | driver.execute_cdp_cmd("Input.dispatchMouseEvent", {"type": "mouseReleased", "x": x, "y": y}) 13 | 14 | 15 | driver.get("https://hmaker.github.io/selenium-detector/") 16 | driver.execute_cdp_cmd("Runtime.disable",{}) 17 | 18 | 19 | driver.find_element(By.CSS_SELECTOR, '#chromedriver-token').send_keys( 20 | driver.execute_script('return window.token') 21 | ) 22 | driver.find_element(By.CSS_SELECTOR, '#chromedriver-asynctoken').send_keys( 23 | driver.execute_async_script('window.getAsyncToken().then(arguments[0])') 24 | ) 25 | driver.find_element(By.CSS_SELECTOR, '#chromedriver-test').click() 26 | input('Test finished, check the result on the browser page.\nPress enter to exit...') 27 | driver.quit() -------------------------------------------------------------------------------- /tests/test_driver.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from selenium_profiles.utils.utils import read_json 4 | from selenium_profiles.webdriver import Chrome 5 | from selenium.webdriver.common.by import By # locate elements 6 | 7 | 8 | 9 | def test_driver(choose: str, headless: bool = True, uc_driver=False): 10 | profile = read_json(filename='profiles\\default.json') 11 | testprofile = profile[choose] 12 | testprofile["cdp"]["patch_version"] = None # don't change version :) 13 | testprofile["options"]["headless"] = headless 14 | driver = Chrome(profile=testprofile, uc_driver=uc_driver) 15 | driver.get('https://browserleaks.com/client-hints') 16 | elem = driver.find_element(By.XPATH, '//*[@id="content"]/table[1]/tbody/tr/td[2]') 17 | useragent = elem.text 18 | exported_profile = driver.profiles.get_profile() 19 | driver.quit() 20 | 21 | print(choose+'\n'+useragent+'\n') 22 | return {"profile": driver.profiles._profile, "exported_profile": exported_profile, "useragent":useragent} 23 | 24 | 25 | class Driver(unittest.TestCase): 26 | # noinspection PyGlobalUndefined 27 | # Hello World! 28 | def test_windows(self): 29 | output = test_driver('Windows', headless=True) 30 | self.assertEqual(output["exported_profile"]["cdp"]["useragent"], 31 | output["profile"]["cdp"]["useragent"]) # add assertion here 32 | 33 | # noinspection PyGlobalUndefined 34 | def test_android(self): 35 | output = test_driver('Android', headless=False) 36 | self.assertEqual(output["exported_profile"]["cdp"]["useragent"], 37 | output["profile"]["cdp"]["useragent"]) # add assertion here 38 | 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | --------------------------------------------------------------------------------