├── LICENSE ├── README.md ├── api.py ├── config.toml ├── run.py ├── server.py ├── static ├── favicon.ico ├── geetest.html └── gt.js ├── test.py └── worker.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ruoyang Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Geetest3 Distributed Cracking Platform

6 | 7 | ## Introduction 8 | 9 | This platform works by transferring Geetest3 validation on websites to distributed workers which simulate human and auto-complete the process. 10 | 11 | A user requests a captcha from a website and submit it to the server, then the cracking task will be randomly assigned to an online worker. The worker will perform sliding puzzle automatically and pass the 2-step verification data back to the user. Post the 2FA data to the website and the validation flow is completed. 12 | 13 | *Learn more about [Geetest Networking Sequence](https://docs.geetest.com/static/install/overview/imgs/geetest_netwoking_sequence.jpg)* 14 | 15 | ## Demo 16 | 17 | [Video](https://cdn.kagamiz.com/Geetest3-Crack/demo.mp4) 18 | 19 | ## Quick Start 20 | 21 | 1. Clone or [download](https://github.com/Hsury/Geetest3-Crack/archive/master.zip) this repository 22 | 23 | 2. Install [Chrome](https://www.google.com/chrome/) and [ChromeDriver](http://chromedriver.chromium.org/), make sure they can be found in PATH variable 24 | 25 | 3. Use pip to install requirements 26 | 27 | ``` 28 | python3.6 -m pip install -U flask gevent pillow requests selenium toml 29 | ``` 30 | 31 | 4. Launch [run.py](https://github.com/Hsury/Geetest3-Crack/blob/master/run.py) to test the platform 32 | 33 | ``` 34 | python3.6 run.py 35 | ``` 36 | 37 | *More configuration can be modified in [config.toml](https://github.com/Hsury/Geetest3-Crack/blob/master/config.toml)* 38 | 39 | ## API 40 | 41 | Use GET method to access API, and the return data is in JSON format 42 | 43 | ### /crack 44 | 45 | #### Param 46 | 47 | - gt 48 | 49 | - challenge 50 | 51 | - success (Optional, default value is 1) 52 | 53 | ``` 54 | http://127.0.0.1:3333/crack?gt=d712df3d362b20bd5b3d290adf7603bc&challenge=dbd7e4f6318d3338f9e698875ecc3a56&success=1 55 | ``` 56 | 57 | #### Return 58 | 59 | - Success 60 | 61 | ``` 62 | {'code': 0, 'message': 'success', 'challenge': 'dbd7e4f6318d3338f9e698875ecc3a5637', 'validate': 'f5d49c5f0a0f5a9dd8a65aef3416737a', 'seccode': 'f5d49c5f0a0f5a9dd8a65aef3416737a|jordan'} 63 | ``` 64 | 65 | - Invalid parameter 66 | 67 | ``` 68 | {'code': -1, 'message': 'invalid parameter'} 69 | ``` 70 | 71 | - Error 72 | 73 | ``` 74 | {'code': -2, 'message': 'error'} 75 | ``` 76 | 77 | - Timeout 78 | 79 | ``` 80 | {'code': -3, 'message': 'timeout'} 81 | ``` 82 | 83 | ### /status 84 | 85 | #### Param 86 | 87 | None 88 | 89 | ``` 90 | http://127.0.0.1:3333/status 91 | ``` 92 | 93 | #### Return 94 | 95 | ``` 96 | {'code': 0, 'workers': 1, 'pending': 0, 'doing': 1, 'done': 39} 97 | ``` 98 | 99 | *Refer to [api.py](https://github.com/Hsury/Geetest3-Crack/blob/master/api.py) and [test.py](https://github.com/Hsury/Geetest3-Crack/blob/master/test.py) for more examples* 100 | 101 | ## Disclaimer 102 | 103 | The project is for study and technical communication only, do not use it for illegal purposes! 104 | 105 | I don't take any responsibility if legal dispute occurs. 106 | 107 | ## License 108 | 109 | Geetest3 Distributed Cracking Platform is under The Star And Thank Author License (SATA) 110 | 111 | You are obliged to star this open source project and consider giving the author appropriate rewards ∠( ᐛ 」∠)_ 112 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | class Geetest3(): 4 | def __init__(self, address="127.0.0.1", port=3333): 5 | self.url = f"http://{address}:{port}" 6 | 7 | def crack(self, gt, challenge, success=1): 8 | try: 9 | return requests.get(f"{self.url}/crack?gt={gt}&challenge={challenge}&success={success}").json() 10 | except: 11 | return None 12 | 13 | def status(self): 14 | try: 15 | return requests.get(f"{self.url}/status").json() 16 | except: 17 | return None 18 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | address = "127.0.0.1" 3 | port = 3333 4 | 5 | [server] 6 | enable = true 7 | 8 | [worker] 9 | enable = true 10 | headless = false 11 | workers = 2 12 | delay = 2 13 | 14 | [test] 15 | enable = true 16 | thread = 2 17 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import toml 4 | from multiprocessing import Process 5 | 6 | def main(): 7 | config_path = sys.argv[1] if len(sys.argv) > 1 else "config.toml" 8 | try: 9 | config = toml.load(config_path) 10 | except: 11 | return 12 | global_config = config.get('global', {}) 13 | server_config = config.get('server', {}) 14 | worker_config = config.get('worker', {}) 15 | test_config = config.get('test', {}) 16 | address = global_config.get('address', "127.0.0.1") 17 | port = global_config.get('port', 3333) 18 | print(f"URL: http://{address}:{port}/") 19 | if server_config.get('enable'): 20 | print("Server: ON") 21 | import server 22 | Process(target=server.main, args=("0.0.0.0", port)).start() 23 | else: 24 | print("Server: OFF") 25 | if worker_config.get('enable'): 26 | print("Worker: ON") 27 | import worker 28 | Process(target=worker.main, args=(address, port, worker_config.get('headless', False), worker_config.get('workers', 2), worker_config.get('delay', 2))).start() 29 | else: 30 | print("Worker: OFF") 31 | if test_config.get('enable'): 32 | print("Test: ON") 33 | import test 34 | for _ in range(test_config.get('thread', 2)): 35 | Process(target=test.main, args=(address, port)).start() 36 | else: 37 | print("Test: OFF") 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from gevent import monkey 2 | monkey.patch_all() 3 | 4 | import threading 5 | import time 6 | from flask import Flask, abort, jsonify, request, send_from_directory 7 | from gevent.pywsgi import WSGIServer 8 | from uuid import uuid4 9 | 10 | app = Flask(__name__) 11 | 12 | cv_put = threading.Condition() 13 | cv_get = threading.Condition() 14 | 15 | workers = 0 16 | 17 | tasks = {} 18 | pending = [] 19 | doing = [] 20 | done = [] 21 | 22 | def main(address="0.0.0.0", port=3333): 23 | http_server = WSGIServer((address, port), app) 24 | http_server.serve_forever() 25 | 26 | @app.route("/") 27 | def root(): 28 | return f"Geetest3 - StatusGeetest3 Distributed Cracking Platform
https://github.com/Hsury/Geetest3-Crack

Workers: {workers + len(doing)}

Pending: {len(pending)}
Doing: {len(doing)}
Done: {len(done)}" 29 | 30 | @app.route("/crack") 31 | def crack(): 32 | if all([key in request.args for key in ['gt', 'challenge']]): 33 | session = str(uuid4()) 34 | tasks[session] = { 35 | 'code': -1, 36 | 'gt': request.args.get('gt'), 37 | 'challenge': request.args.get('challenge'), 38 | 'success': request.args.get('success', 1), 39 | 'validate': "", 40 | 'seccode': "", 41 | } 42 | pending.append(session) 43 | with cv_put: 44 | cv_put.notify_all() 45 | with cv_get: 46 | if cv_get.wait_for(lambda: session in done, timeout=30): 47 | # done.remove(session) 48 | if tasks[session]['code'] == 0: 49 | return jsonify({ 50 | 'code': 0, 51 | 'message': "success", 52 | 'challenge': tasks[session]['challenge'], 53 | 'validate': tasks[session]['validate'], 54 | 'seccode': tasks[session]['seccode'], 55 | }) 56 | else: 57 | return jsonify({ 58 | 'code': -2, 59 | 'message': "error", 60 | }) 61 | else: 62 | if session in pending: 63 | pending.remove(session) 64 | if session in doing: 65 | doing.remove(session) 66 | done.append(session) 67 | return jsonify({ 68 | 'code': -3, 69 | 'message': "timeout", 70 | }) 71 | del tasks[session] 72 | else: 73 | return jsonify({ 74 | 'code': -1, 75 | 'message': "invalid parameter", 76 | }) 77 | 78 | @app.route('/favicon.ico') 79 | def favicon(): 80 | return send_from_directory("static", "favicon.ico", mimetype="image/vnd.microsoft.icon") 81 | 82 | @app.route("/feedback", methods=['POST']) 83 | def feedback(): 84 | if all([key in request.form for key in ['session', 'code']]): 85 | session = request.form.get('session') 86 | if session in doing: 87 | if request.form.get('code') == "0" and all([key in request.form for key in ['challenge', 'validate', 'seccode']]): 88 | tasks[session]['code'] = 0 89 | tasks[session]['challenge'] = request.form.get('challenge') 90 | tasks[session]['validate'] = request.form.get('validate') 91 | tasks[session]['seccode'] = request.form.get('seccode') 92 | doing.remove(session) 93 | done.append(session) 94 | with cv_get: 95 | cv_get.notify_all() 96 | return jsonify({ 97 | 'code': 0, 98 | 'message': "success", 99 | }) 100 | else: 101 | return jsonify({ 102 | 'code': -2, 103 | 'message': "invalid session", 104 | }) 105 | else: 106 | return jsonify({ 107 | 'code': -1, 108 | 'message': "invalid parameter", 109 | }) 110 | 111 | @app.route("/fetch") 112 | def fetch(): 113 | global workers 114 | workers += 1 115 | with cv_put: 116 | if cv_put.wait_for(lambda: pending, timeout=15): 117 | session = pending.pop(0) 118 | doing.append(session) 119 | workers -= 1 120 | return jsonify({ 121 | 'session': session, 122 | 'gt': tasks[session]['gt'], 123 | 'challenge': tasks[session]['challenge'], 124 | 'success': tasks[session]['success'], 125 | }) 126 | else: 127 | workers -= 1 128 | abort(503) 129 | 130 | @app.route("/status") 131 | def status(): 132 | return jsonify({ 133 | 'code': 0, 134 | 'workers': workers + len(doing), 135 | 'pending': len(pending), 136 | 'doing': len(doing), 137 | 'done': len(done), 138 | }) 139 | 140 | @app.route("/task") 141 | def task(): 142 | return app.send_static_file("geetest.html") 143 | 144 | if __name__ == "__main__": 145 | main() 146 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hsury/Geetest3-Crack/c5298442bd75eb3f91047ffb552d91affd41ad4f/static/favicon.ico -------------------------------------------------------------------------------- /static/geetest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Geetest3 - Task Receiver 8 | 9 | 10 | 11 |

接收任务

12 |

13 |

14 |

15 |

16 |

17 | 18 | 19 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /static/gt.js: -------------------------------------------------------------------------------- 1 | "v0.4.6 Geetest Inc."; 2 | 3 | (function (window) { 4 | "use strict"; 5 | if (typeof window === 'undefined') { 6 | throw new Error('Geetest requires browser environment'); 7 | } 8 | 9 | var document = window.document; 10 | var Math = window.Math; 11 | var head = document.getElementsByTagName("head")[0]; 12 | 13 | function _Object(obj) { 14 | this._obj = obj; 15 | } 16 | 17 | _Object.prototype = { 18 | _each: function (process) { 19 | var _obj = this._obj; 20 | for (var k in _obj) { 21 | if (_obj.hasOwnProperty(k)) { 22 | process(k, _obj[k]); 23 | } 24 | } 25 | return this; 26 | } 27 | }; 28 | 29 | function Config(config) { 30 | var self = this; 31 | new _Object(config)._each(function (key, value) { 32 | self[key] = value; 33 | }); 34 | } 35 | 36 | Config.prototype = { 37 | api_server: 'api.geetest.com', 38 | protocol: 'http://', 39 | typePath: '/gettype.php', 40 | fallback_config: { 41 | slide: { 42 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 43 | type: 'slide', 44 | slide: '/static/js/geetest.0.0.0.js' 45 | }, 46 | fullpage: { 47 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 48 | type: 'fullpage', 49 | fullpage: '/static/js/fullpage.0.0.0.js' 50 | } 51 | }, 52 | _get_fallback_config: function () { 53 | var self = this; 54 | if (isString(self.type)) { 55 | return self.fallback_config[self.type]; 56 | } else if (self.new_captcha) { 57 | return self.fallback_config.fullpage; 58 | } else { 59 | return self.fallback_config.slide; 60 | } 61 | }, 62 | _extend: function (obj) { 63 | var self = this; 64 | new _Object(obj)._each(function (key, value) { 65 | self[key] = value; 66 | }) 67 | } 68 | }; 69 | var isNumber = function (value) { 70 | return (typeof value === 'number'); 71 | }; 72 | var isString = function (value) { 73 | return (typeof value === 'string'); 74 | }; 75 | var isBoolean = function (value) { 76 | return (typeof value === 'boolean'); 77 | }; 78 | var isObject = function (value) { 79 | return (typeof value === 'object' && value !== null); 80 | }; 81 | var isFunction = function (value) { 82 | return (typeof value === 'function'); 83 | }; 84 | 85 | var callbacks = {}; 86 | var status = {}; 87 | 88 | var random = function () { 89 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 90 | }; 91 | 92 | var loadScript = function (url, cb) { 93 | var script = document.createElement("script"); 94 | script.charset = "UTF-8"; 95 | script.async = true; 96 | 97 | script.onerror = function () { 98 | cb(true); 99 | }; 100 | var loaded = false; 101 | script.onload = script.onreadystatechange = function () { 102 | if (!loaded && 103 | (!script.readyState || 104 | "loaded" === script.readyState || 105 | "complete" === script.readyState)) { 106 | 107 | loaded = true; 108 | setTimeout(function () { 109 | cb(false); 110 | }, 0); 111 | } 112 | }; 113 | script.src = url; 114 | head.appendChild(script); 115 | }; 116 | 117 | var normalizeDomain = function (domain) { 118 | // special domain: uems.sysu.edu.cn/jwxt/geetest/ 119 | // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn 120 | return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest 121 | }; 122 | var normalizePath = function (path) { 123 | path = path.replace(/\/+/g, '/'); 124 | if (path.indexOf('/') !== 0) { 125 | path = '/' + path; 126 | } 127 | return path; 128 | }; 129 | var normalizeQuery = function (query) { 130 | if (!query) { 131 | return ''; 132 | } 133 | var q = '?'; 134 | new _Object(query)._each(function (key, value) { 135 | if (isString(value) || isNumber(value) || isBoolean(value)) { 136 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 137 | } 138 | }); 139 | if (q === '?') { 140 | q = ''; 141 | } 142 | return q.replace(/&$/, ''); 143 | }; 144 | var makeURL = function (protocol, domain, path, query) { 145 | domain = normalizeDomain(domain); 146 | 147 | var url = normalizePath(path) + normalizeQuery(query); 148 | if (domain) { 149 | url = protocol + domain + url; 150 | } 151 | 152 | return url; 153 | }; 154 | 155 | var load = function (protocol, domains, path, query, cb) { 156 | var tryRequest = function (at) { 157 | 158 | var url = makeURL(protocol, domains[at], path, query); 159 | loadScript(url, function (err) { 160 | if (err) { 161 | if (at >= domains.length - 1) { 162 | cb(true); 163 | } else { 164 | tryRequest(at + 1); 165 | } 166 | } else { 167 | cb(false); 168 | } 169 | }); 170 | }; 171 | tryRequest(0); 172 | }; 173 | 174 | 175 | var jsonp = function (domains, path, config, callback) { 176 | if (isObject(config.getLib)) { 177 | config._extend(config.getLib); 178 | callback(config); 179 | return; 180 | } 181 | if (config.offline) { 182 | callback(config._get_fallback_config()); 183 | return; 184 | } 185 | 186 | var cb = "geetest_" + random(); 187 | window[cb] = function (data) { 188 | if (data.status == 'success') { 189 | callback(data.data); 190 | } else if (!data.status) { 191 | callback(data); 192 | } else { 193 | callback(config._get_fallback_config()); 194 | } 195 | window[cb] = undefined; 196 | try { 197 | delete window[cb]; 198 | } catch (e) { 199 | } 200 | }; 201 | load(config.protocol, domains, path, { 202 | gt: config.gt, 203 | callback: cb 204 | }, function (err) { 205 | if (err) { 206 | callback(config._get_fallback_config()); 207 | } 208 | }); 209 | }; 210 | 211 | var throwError = function (errorType, config) { 212 | var errors = { 213 | networkError: '网络错误', 214 | gtTypeError: 'gt字段不是字符串类型' 215 | }; 216 | if (typeof config.onError === 'function') { 217 | config.onError(errors[errorType]); 218 | } else { 219 | throw new Error(errors[errorType]); 220 | } 221 | }; 222 | 223 | var detect = function () { 224 | return window.Geetest || document.getElementById("gt_lib"); 225 | }; 226 | 227 | if (detect()) { 228 | status.slide = "loaded"; 229 | } 230 | 231 | window.initGeetest = function (userConfig, callback) { 232 | 233 | var config = new Config(userConfig); 234 | 235 | if (userConfig.https) { 236 | config.protocol = 'https://'; 237 | } else if (!userConfig.protocol) { 238 | config.protocol = window.location.protocol + '//'; 239 | } 240 | 241 | // for KFC 242 | if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || 243 | userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { 244 | config.apiserver = 'yumchina.geetest.com/'; // for old js 245 | config.api_server = 'yumchina.geetest.com'; 246 | } 247 | 248 | if (isObject(userConfig.getType)) { 249 | config._extend(userConfig.getType); 250 | } 251 | jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) { 252 | var type = newConfig.type; 253 | var init = function () { 254 | config._extend(newConfig); 255 | callback(new window.Geetest(config)); 256 | }; 257 | 258 | callbacks[type] = callbacks[type] || []; 259 | var s = status[type] || 'init'; 260 | if (s === 'init') { 261 | status[type] = 'loading'; 262 | 263 | callbacks[type].push(init); 264 | 265 | load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 266 | if (err) { 267 | status[type] = 'fail'; 268 | throwError('networkError', config); 269 | } else { 270 | status[type] = 'loaded'; 271 | var cbs = callbacks[type]; 272 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 273 | var cb = cbs[i]; 274 | if (isFunction(cb)) { 275 | cb(); 276 | } 277 | } 278 | callbacks[type] = []; 279 | } 280 | }); 281 | } else if (s === "loaded") { 282 | init(); 283 | } else if (s === "fail") { 284 | throwError('networkError', config); 285 | } else if (s === "loading") { 286 | callbacks[type].push(init); 287 | } 288 | }); 289 | 290 | }; 291 | 292 | 293 | })(window); -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from api import Geetest3 4 | 5 | def main(address="127.0.0.1", port=3333): 6 | geetest3 = Geetest3(address=address, port=port) 7 | while True: 8 | captcha = get_captcha() 9 | if captcha: 10 | result = geetest3.crack(gt=captcha.get('gt'), challenge=captcha.get('challenge'), success=captcha.get('success')) 11 | status = geetest3.status() 12 | print("=" * 100) 13 | print(f"Captcha: {captcha}") 14 | print(f"Result: {result}") 15 | print(f"Status: {status}") 16 | print("=" * 100) 17 | time.sleep(1) 18 | 19 | def get_captcha(): 20 | try: 21 | response = requests.get("https://passport.bilibili.com/web/captcha/combine?plat=5").json() 22 | if response.get('code') == 0 and response.get('data', {}).get('type') == 1: 23 | return response.get('data', {}).get('result') 24 | except: 25 | pass 26 | return None 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /worker.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | import platform 4 | import random 5 | import time 6 | from concurrent.futures import ThreadPoolExecutor 7 | from PIL import Image 8 | from selenium import webdriver 9 | from selenium.webdriver.common.action_chains import ActionChains 10 | 11 | def main(address="127.0.0.1", port=3333, headless=False, workers=2, delay=2): 12 | def task(thread_id): 13 | time.sleep(thread_id * delay) 14 | driver = create_driver(f"http://{address}:{port}/task", headless=headless) 15 | while True: 16 | try: 17 | crack_geetest(driver) 18 | time.sleep(2) 19 | except: 20 | time.sleep(1) 21 | 22 | if not os.path.exists("tmp"): 23 | os.makedirs("tmp") 24 | with ThreadPoolExecutor(max_workers=workers) as executor: 25 | executor.map(task, range(workers)) 26 | 27 | def create_driver(url, headless=False): 28 | options = webdriver.ChromeOptions() 29 | options.add_argument("log-level=3") 30 | if headless: 31 | options.add_argument("headless") 32 | else: 33 | options.add_argument("disable-infobars") 34 | options.add_argument("window-size=380,460") 35 | if platform.system() == "Linux": 36 | options.add_argument("no-sandbox") 37 | driver = webdriver.Chrome(options=options) 38 | driver.get(url) 39 | return driver 40 | 41 | def crack_geetest(driver, bg_class="geetest_canvas_bg geetest_absolute", full_bg_class="geetest_canvas_fullbg geetest_fade geetest_absolute", slider_class="geetest_slider_button", success_class="geetest_success"): 42 | driver.find_element_by_class_name(slider_class) 43 | rand = str(random.randint(0, 100000000)) 44 | bg_path = save_bg(driver, bg_path=os.path.join("tmp", f"bg_{rand}.png"), bg_class=bg_class) 45 | full_bg_path = save_full_bg(driver, full_bg_path=os.path.join("tmp", f"fullbg_{rand}.png"), full_bg_class=full_bg_class) 46 | distance = get_offset(full_bg_path, bg_path) - 4 47 | os.remove(full_bg_path) 48 | os.remove(bg_path) 49 | track = get_track(distance) 50 | drag_the_ball(driver, track, slider_class=slider_class) 51 | 52 | def save_base64img(data_str, save_name): 53 | img_data = base64.b64decode(data_str) 54 | file = open(save_name, "wb") 55 | file.write(img_data) 56 | file.close() 57 | 58 | def get_base64_by_canvas(driver, class_name, contain_type): 59 | bg_img = "" 60 | while len(bg_img) < 5000: 61 | get_img_js = f"return document.getElementsByClassName(\"{class_name}\")[0].toDataURL(\"image/png\");" 62 | bg_img = driver.execute_script(get_img_js) 63 | time.sleep(0.5) 64 | if contain_type: 65 | return bg_img 66 | else: 67 | return bg_img[bg_img.find(",") + 1:] 68 | 69 | def save_bg(driver, bg_path="bg.png", bg_class="geetest_canvas_bg geetest_absolute"): 70 | bg_img_data = get_base64_by_canvas(driver, bg_class, False) 71 | save_base64img(bg_img_data, bg_path) 72 | return bg_path 73 | 74 | def save_full_bg(driver, full_bg_path="fbg.png", full_bg_class="geetest_canvas_fullbg geetest_fade geetest_absolute"): 75 | bg_img_data = get_base64_by_canvas(driver, full_bg_class, False) 76 | save_base64img(bg_img_data, full_bg_path) 77 | return full_bg_path 78 | 79 | def get_slider(driver, slider_class="geetest_slider_button"): 80 | while True: 81 | try: 82 | slider = driver.find_element_by_class_name(slider_class) 83 | break 84 | except: 85 | time.sleep(0.5) 86 | return slider 87 | 88 | def is_pixel_equal(img1, img2, x, y): 89 | pix1 = img1.load()[x, y] 90 | pix2 = img2.load()[x, y] 91 | threshold = 60 92 | if abs(pix1[0] - pix2[0] < threshold) and abs(pix1[1] - pix2[1] < threshold) and abs(pix1[2] - pix2[2] < threshold): 93 | return True 94 | else: 95 | return False 96 | 97 | def get_offset(full_bg_path, bg_path, initial_offset=39): 98 | full_bg = Image.open(full_bg_path) 99 | bg = Image.open(bg_path) 100 | left = initial_offset 101 | for i in range(left, full_bg.size[0]): 102 | for j in range(full_bg.size[1]): 103 | if not is_pixel_equal(full_bg, bg, i, j): 104 | left = i 105 | return left 106 | return left 107 | 108 | def get_track(distance): 109 | track = [] 110 | current = 0 111 | mid = distance * 3 / 4 112 | t = random.randint(2, 3) / 10 113 | v = 0 114 | while current < distance: 115 | if current < mid: 116 | a = 2 117 | else: 118 | a = -3 119 | v0 = v 120 | v = v0 + a * t 121 | move = v0 * t + 1 / 2 * a * t * t 122 | current += move 123 | track.append(round(move)) 124 | return track 125 | 126 | def drag_the_ball(driver, track, slider_class="geetest_slider_button"): 127 | slider = get_slider(driver, slider_class) 128 | ActionChains(driver).click_and_hold(slider).perform() 129 | while track: 130 | x = random.choice(track) 131 | ActionChains(driver).move_by_offset(xoffset=x, yoffset=0).perform() 132 | track.remove(x) 133 | time.sleep(0.1) 134 | imitate = ActionChains(driver).move_by_offset(xoffset=-1, yoffset=0) 135 | time.sleep(0.015) 136 | imitate.perform() 137 | # time.sleep(random.randint(6, 10) / 10) 138 | imitate.perform() 139 | time.sleep(0.04) 140 | imitate.perform() 141 | time.sleep(0.012) 142 | imitate.perform() 143 | time.sleep(0.019) 144 | imitate.perform() 145 | time.sleep(0.033) 146 | ActionChains(driver).move_by_offset(xoffset=1, yoffset=0).perform() 147 | ActionChains(driver).pause(random.randint(6, 14) / 10).release(slider).perform() 148 | 149 | if __name__ == "__main__": 150 | main() 151 | --------------------------------------------------------------------------------