tiktok-captcha-solver 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tiktok_captcha_solver-0.0.1/PKG-INFO +62 -0
- tiktok_captcha_solver-0.0.1/README.md +39 -0
- tiktok_captcha_solver-0.0.1/pyproject.toml +39 -0
- tiktok_captcha_solver-0.0.1/setup.cfg +4 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/__init__.py +0 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/api.py +44 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/downloader.py +7 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/models.py +13 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/sadcaptcha.py +183 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/tests/__init__.py +0 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/tests/test_api.py +28 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/tests/test_downloader.py +8 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver/tests/test_sadcaptcha.py +47 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver.egg-info/PKG-INFO +62 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver.egg-info/SOURCES.txt +16 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver.egg-info/dependency_links.txt +1 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver.egg-info/requires.txt +6 -0
- tiktok_captcha_solver-0.0.1/tiktok_captcha_solver.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tiktok-captcha-solver
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: This package integrates with Selenium to solve any TikTok captcha in one line of code.
|
|
5
|
+
Author-email: Toughdata LLC <greg@toughdata.net>
|
|
6
|
+
Project-URL: Homepage, https://www.sadcaptcha.com
|
|
7
|
+
Project-URL: Source, https://github.com/gbiz123/tiktok-captcha-solver/
|
|
8
|
+
Keywords: tiktok,captcha,solver,selenium,rotate,puzzle,3d
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: selenium
|
|
18
|
+
Requires-Dist: webdriver-manager
|
|
19
|
+
Requires-Dist: pydantic
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: pytest
|
|
22
|
+
Requires-Dist: undetected_chromedriver
|
|
23
|
+
|
|
24
|
+
# TikTok Captcha Solver API
|
|
25
|
+
This project is the [SadCaptcha TikTok Captcha Solver](https://www.sadcaptcha.com?ref=ghclientrepo) API client.
|
|
26
|
+
The purpose is to make integrating SadCaptcha into your selenium app as simple as one line of code.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
- Python >= 3.10
|
|
30
|
+
- Selenium properly installed and in `PATH`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
This project will be available as a `pip` package soon.
|
|
34
|
+
For now, just clone the repo and build it using the following commands:
|
|
35
|
+
```
|
|
36
|
+
git clone https://github.com/gbiz123/tiktok-captcha-solver
|
|
37
|
+
cd tiktok-captcha-solver
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
Import the package, set up the SadCaptcha class, and call it whenever you need.
|
|
43
|
+
|
|
44
|
+
```py
|
|
45
|
+
from sadcaptcha import SadCaptcha
|
|
46
|
+
import undetected_chromedriver as uc
|
|
47
|
+
|
|
48
|
+
driver = uc.Chrome(headless=False)
|
|
49
|
+
api_key = "YOUR_API_KEY_HERE"
|
|
50
|
+
sadcaptcha = SadCaptcha(driver, api_key)
|
|
51
|
+
|
|
52
|
+
# Selenium code that causes a TikTok captcha...
|
|
53
|
+
|
|
54
|
+
sadcaptcha.solve_captcha_if_present()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it!
|
|
58
|
+
|
|
59
|
+
## Contact
|
|
60
|
+
- Homepage: https://www.sadcaptcha.com/
|
|
61
|
+
- Email: info@toughdata.net
|
|
62
|
+
- Telegram @toughdata
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# TikTok Captcha Solver API
|
|
2
|
+
This project is the [SadCaptcha TikTok Captcha Solver](https://www.sadcaptcha.com?ref=ghclientrepo) API client.
|
|
3
|
+
The purpose is to make integrating SadCaptcha into your selenium app as simple as one line of code.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
- Python >= 3.10
|
|
7
|
+
- Selenium properly installed and in `PATH`
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
This project will be available as a `pip` package soon.
|
|
11
|
+
For now, just clone the repo and build it using the following commands:
|
|
12
|
+
```
|
|
13
|
+
git clone https://github.com/gbiz123/tiktok-captcha-solver
|
|
14
|
+
cd tiktok-captcha-solver
|
|
15
|
+
pip install -e .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
Import the package, set up the SadCaptcha class, and call it whenever you need.
|
|
20
|
+
|
|
21
|
+
```py
|
|
22
|
+
from sadcaptcha import SadCaptcha
|
|
23
|
+
import undetected_chromedriver as uc
|
|
24
|
+
|
|
25
|
+
driver = uc.Chrome(headless=False)
|
|
26
|
+
api_key = "YOUR_API_KEY_HERE"
|
|
27
|
+
sadcaptcha = SadCaptcha(driver, api_key)
|
|
28
|
+
|
|
29
|
+
# Selenium code that causes a TikTok captcha...
|
|
30
|
+
|
|
31
|
+
sadcaptcha.solve_captcha_if_present()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That's it!
|
|
35
|
+
|
|
36
|
+
## Contact
|
|
37
|
+
- Homepage: https://www.sadcaptcha.com/
|
|
38
|
+
- Email: info@toughdata.net
|
|
39
|
+
- Telegram @toughdata
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta" # If not defined, then legacy behavior can happen.
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tiktok-captcha-solver"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
|
|
9
|
+
description = "This package integrates with Selenium to solve any TikTok captcha in one line of code."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
|
|
13
|
+
keywords = ["tiktok", "captcha", "solver", "selenium", "rotate", "puzzle", "3d"]
|
|
14
|
+
|
|
15
|
+
authors = [
|
|
16
|
+
{name = "Toughdata LLC", email = "greg@toughdata.net" }
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Topic :: Software Development :: Build Tools",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
"selenium",
|
|
30
|
+
"webdriver-manager",
|
|
31
|
+
"pydantic",
|
|
32
|
+
"requests",
|
|
33
|
+
"pytest",
|
|
34
|
+
"undetected_chromedriver"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
"Homepage" = "https://www.sadcaptcha.com"
|
|
39
|
+
"Source" = "https://github.com/gbiz123/tiktok-captcha-solver/"
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from .models import ShapesCaptchaResponse, RotateCaptchaResponse, PuzzleCaptchaResponse
|
|
5
|
+
|
|
6
|
+
class ApiClient:
|
|
7
|
+
|
|
8
|
+
_PUZZLE_URL: str
|
|
9
|
+
_ROTATE_URL: str
|
|
10
|
+
_SHAPES_URL: str
|
|
11
|
+
|
|
12
|
+
def __init__(self, api_key: str) -> None:
|
|
13
|
+
self._PUZZLE_URL = "https://www.sadcaptcha.com/api/v1/puzzle?licenseKey=" + api_key
|
|
14
|
+
self._ROTATE_URL = "https://www.sadcaptcha.com/api/v1/rotate?licenseKey=" + api_key
|
|
15
|
+
self._SHAPES_URL = "https://www.sadcaptcha.com/api/v1/shapes?licenseKey=" + api_key
|
|
16
|
+
|
|
17
|
+
def rotate(self, outer_b46: str, inner_b64: str) -> RotateCaptchaResponse:
|
|
18
|
+
data = {
|
|
19
|
+
"outerImageB64": outer_b46,
|
|
20
|
+
"innerImageB64": inner_b64
|
|
21
|
+
}
|
|
22
|
+
resp = requests.post(self._ROTATE_URL, json=data)
|
|
23
|
+
result = resp.json()
|
|
24
|
+
return RotateCaptchaResponse(angle=result.get("angle"))
|
|
25
|
+
|
|
26
|
+
def puzzle(self, puzzle_b64: str, piece_b64: str) -> PuzzleCaptchaResponse:
|
|
27
|
+
data = {
|
|
28
|
+
"puzzleImageB64": puzzle_b64,
|
|
29
|
+
"pieceImageB64": piece_b64
|
|
30
|
+
}
|
|
31
|
+
resp = requests.post(self._PUZZLE_URL, json=data)
|
|
32
|
+
result = resp.json()
|
|
33
|
+
return PuzzleCaptchaResponse(slide_x_proportion=result.get("slideXProportion"))
|
|
34
|
+
|
|
35
|
+
def shapes(self, image_b64: str) -> ShapesCaptchaResponse:
|
|
36
|
+
data = { "imageB64": image_b64 }
|
|
37
|
+
resp = requests.post(self._SHAPES_URL, json=data)
|
|
38
|
+
result = resp.json()
|
|
39
|
+
return ShapesCaptchaResponse(
|
|
40
|
+
point_one_proportion_x=result.get("pointOneProportionX"),
|
|
41
|
+
point_one_proportion_y=result.get("pointOneProportionY"),
|
|
42
|
+
point_two_proportion_x=result.get("pointTwoProportionX"),
|
|
43
|
+
point_two_proportion_y=result.get("pointTwoProportionX")
|
|
44
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
class ShapesCaptchaResponse(BaseModel):
|
|
4
|
+
point_one_proportion_x: float
|
|
5
|
+
point_one_proportion_y: float
|
|
6
|
+
point_two_proportion_x: float
|
|
7
|
+
point_two_proportion_y: float
|
|
8
|
+
|
|
9
|
+
class RotateCaptchaResponse(BaseModel):
|
|
10
|
+
angle: int
|
|
11
|
+
|
|
12
|
+
class PuzzleCaptchaResponse(BaseModel):
|
|
13
|
+
slide_x_proportion: float
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""This class handles the captcha solving"""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from selenium.webdriver import ActionChains, Chrome
|
|
6
|
+
from selenium.webdriver.common.by import By
|
|
7
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
8
|
+
from undetected_chromedriver import logging
|
|
9
|
+
|
|
10
|
+
from .api import ApiClient
|
|
11
|
+
from .downloader import download_image_b64
|
|
12
|
+
|
|
13
|
+
class SadCaptcha:
|
|
14
|
+
|
|
15
|
+
client: ApiClient
|
|
16
|
+
chromedriver: Chrome
|
|
17
|
+
|
|
18
|
+
def __init__(self, chromedriver: Chrome, sadcaptcha_api_key: str) -> None:
|
|
19
|
+
self.chromedriver = chromedriver
|
|
20
|
+
self.client = ApiClient(sadcaptcha_api_key)
|
|
21
|
+
|
|
22
|
+
def solve_captcha_if_present(self, captcha_detect_timeout: int = 15, retries: int = 3) -> None:
|
|
23
|
+
"""Solves any captcha that is present, if one is detected
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
captcha_detect_timeout: return if no captcha is detected in this many seconds
|
|
27
|
+
retries: number of times to retry captcha
|
|
28
|
+
"""
|
|
29
|
+
if not self.captcha_is_present(captcha_detect_timeout):
|
|
30
|
+
return
|
|
31
|
+
match self.identify_captcha():
|
|
32
|
+
case "puzzle":
|
|
33
|
+
self.solve_puzzle(retries)
|
|
34
|
+
case "rotate":
|
|
35
|
+
self.solve_rotate(retries)
|
|
36
|
+
case "shapes":
|
|
37
|
+
self.solve_shapes(retries)
|
|
38
|
+
|
|
39
|
+
def captcha_is_present(self, timeout: int = 15) -> bool:
|
|
40
|
+
for _ in range(timeout):
|
|
41
|
+
if len(self.chromedriver.find_elements(By.CSS_SELECTOR, "div#captcha_container")) > 0:
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def identify_captcha(self) -> Literal["puzzle", "shapes", "rotate"]:
|
|
46
|
+
rotate_selector = "[data-testid=whirl-inner-img]"
|
|
47
|
+
puzzle_selector = "img.captcha_verify_img_slide"
|
|
48
|
+
shapes_selector = "#verify-points"
|
|
49
|
+
for _ in range(15):
|
|
50
|
+
if len(self.chromedriver.find_elements(By.CSS_SELECTOR, puzzle_selector)) > 0:
|
|
51
|
+
return "puzzle"
|
|
52
|
+
elif len(self.chromedriver.find_elements(By.CSS_SELECTOR, rotate_selector)) > 0:
|
|
53
|
+
return "rotate"
|
|
54
|
+
elif len(self.chromedriver.find_elements(By.CSS_SELECTOR, shapes_selector)) > 0:
|
|
55
|
+
return "shapes"
|
|
56
|
+
else:
|
|
57
|
+
time.sleep(2)
|
|
58
|
+
raise ValueError("Neither puzzle, shapes, or rotate captcha was present.")
|
|
59
|
+
|
|
60
|
+
def solve_shapes(self, retries: int = 3) -> None:
|
|
61
|
+
for _ in range(retries):
|
|
62
|
+
image = download_image_b64(self._get_shapes_image_url())
|
|
63
|
+
solution = self.client.shapes(image)
|
|
64
|
+
image_element = self.chromedriver.find_element(By.CSS_SELECTOR, "#captcha-verify-image")
|
|
65
|
+
self._click_proportional(image_element, solution.point_one_proportion_x, solution.point_one_proportion_y)
|
|
66
|
+
self._click_proportional(image_element, solution.point_two_proportion_y, solution.point_two_proportion_y)
|
|
67
|
+
self.chromedriver.find_element(By.CSS_SELECTOR, ".verify-captcha-submit-button").click()
|
|
68
|
+
if self._check_captcha_success():
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
def solve_rotate(self, retries: int = 3) -> None:
|
|
72
|
+
for _ in range(retries):
|
|
73
|
+
outer = download_image_b64(self._get_rotate_outer_image_url())
|
|
74
|
+
inner = download_image_b64(self._get_rotate_inner_image_url())
|
|
75
|
+
solution = self.client.rotate(outer, inner)
|
|
76
|
+
distance = self._compute_rotate_slide_distance(solution.angle)
|
|
77
|
+
self._drag_element(".secsdk-captcha-drag-icon", distance, 0)
|
|
78
|
+
if self._check_captcha_success():
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
def solve_puzzle(self, retries: int = 3) -> None:
|
|
82
|
+
for _ in range(retries):
|
|
83
|
+
puzzle = download_image_b64(self._get_puzzle_image_url())
|
|
84
|
+
piece = download_image_b64(self._get_piece_image_url())
|
|
85
|
+
solution = self.client.puzzle(puzzle, piece)
|
|
86
|
+
distance = self._compute_puzzle_slide_distance(solution.slide_x_proportion)
|
|
87
|
+
self._drag_element(".secsdk-captcha-drag-icon", distance, 0)
|
|
88
|
+
if self._check_captcha_success():
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
def _compute_rotate_slide_distance(self, angle: int) -> int:
|
|
92
|
+
slide_length = self._get_slide_length()
|
|
93
|
+
icon_length = self._get_slide_icon_length()
|
|
94
|
+
return int(((slide_length - icon_length) * angle) / 360)
|
|
95
|
+
|
|
96
|
+
def _compute_puzzle_slide_distance(self, proportion_x: float) -> int:
|
|
97
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, "#captcha-verify-image")
|
|
98
|
+
return int(proportion_x * e.size["width"])
|
|
99
|
+
|
|
100
|
+
def _get_slide_length(self) -> int:
|
|
101
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, ".captcha_verify_slide--slidebar")
|
|
102
|
+
return e.size['width']
|
|
103
|
+
|
|
104
|
+
def _get_slide_icon_length(self) -> int:
|
|
105
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, ".secsdk-captcha-drag-icon")
|
|
106
|
+
return e.size['width']
|
|
107
|
+
|
|
108
|
+
def _get_rotate_inner_image_url(self) -> str:
|
|
109
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, "[data-testid=whirl-inner-img]")
|
|
110
|
+
url = e.get_attribute("src")
|
|
111
|
+
if not url:
|
|
112
|
+
raise ValueError("Inner image URL was None")
|
|
113
|
+
return url
|
|
114
|
+
|
|
115
|
+
def _get_rotate_outer_image_url(self) -> str:
|
|
116
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, "[data-testid=whirl-outer-img]")
|
|
117
|
+
url = e.get_attribute("src")
|
|
118
|
+
if not url:
|
|
119
|
+
raise ValueError("Outer image URL was None")
|
|
120
|
+
return url
|
|
121
|
+
|
|
122
|
+
def _get_puzzle_image_url(self) -> str:
|
|
123
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, "#captcha-verify-image")
|
|
124
|
+
url = e.get_attribute("src")
|
|
125
|
+
if not url:
|
|
126
|
+
raise ValueError("Puzzle image URL was None")
|
|
127
|
+
return url
|
|
128
|
+
|
|
129
|
+
def _get_piece_image_url(self) -> str:
|
|
130
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, ".captcha_verify_img_slide")
|
|
131
|
+
url = e.get_attribute("src")
|
|
132
|
+
if not url:
|
|
133
|
+
raise ValueError("Piece image URL was None")
|
|
134
|
+
return url
|
|
135
|
+
|
|
136
|
+
def _get_shapes_image_url(self) -> str:
|
|
137
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, "#captcha-verify-image")
|
|
138
|
+
url = e.get_attribute("src")
|
|
139
|
+
if not url:
|
|
140
|
+
raise ValueError("Shapes image URL was None")
|
|
141
|
+
return url
|
|
142
|
+
|
|
143
|
+
def _check_captcha_success(self) -> bool:
|
|
144
|
+
success_xpath = "//*[contains(text(), 'Verification complete')]"
|
|
145
|
+
for _ in range(20):
|
|
146
|
+
if self.chromedriver.find_elements(By.XPATH, success_xpath):
|
|
147
|
+
return True
|
|
148
|
+
time.sleep(1)
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def _click_proportional(
|
|
152
|
+
self,
|
|
153
|
+
element: WebElement,
|
|
154
|
+
proportion_x: float,
|
|
155
|
+
proportion_y: float
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Click an element inside its bounding box at a point defined by the proportions of x and y
|
|
158
|
+
to the width and height of the entire element
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
element: WebElement to click inside
|
|
162
|
+
proportion_x: float from 0 to 1 defining the proportion x location to click
|
|
163
|
+
proportion_y: float from 0 to 1 defining the proportion y location to click
|
|
164
|
+
"""
|
|
165
|
+
offset_x = (proportion_x * element.size["width"])
|
|
166
|
+
offset_y = (proportion_y * element.size["height"])
|
|
167
|
+
ActionChains(self.chromedriver) \
|
|
168
|
+
.move_to_element_with_offset(element, offset_x, offset_y) \
|
|
169
|
+
.click() \
|
|
170
|
+
.perform()
|
|
171
|
+
time.sleep(1.337)
|
|
172
|
+
|
|
173
|
+
def _drag_element(self, css_selector: str, x: int, y: int) -> None:
|
|
174
|
+
e = self.chromedriver.find_element(By.CSS_SELECTOR, css_selector)
|
|
175
|
+
actions = ActionChains(self.chromedriver, duration=500)
|
|
176
|
+
actions.click_and_hold(e)
|
|
177
|
+
actions.move_by_offset(x - 15, 0)
|
|
178
|
+
time.sleep(0.001)
|
|
179
|
+
for _ in range(0, 15):
|
|
180
|
+
actions.move_by_offset(1, 0)
|
|
181
|
+
time.sleep(0.1)
|
|
182
|
+
actions.release().perform()
|
|
183
|
+
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from ..downloader import download_image_b64
|
|
5
|
+
from ..api import ApiClient
|
|
6
|
+
from tiktok_captcha_solver.models import PuzzleCaptchaResponse, RotateCaptchaResponse, ShapesCaptchaResponse
|
|
7
|
+
|
|
8
|
+
api_client = ApiClient(os.environ["API_KEY"])
|
|
9
|
+
|
|
10
|
+
def test_rotate(caplog):
|
|
11
|
+
caplog.set_level(logging.DEBUG)
|
|
12
|
+
inner = download_image_b64("https://raw.githubusercontent.com/gbiz123/sadcaptcha-code-examples/master/images/inner_tt.png")
|
|
13
|
+
outer = download_image_b64("https://raw.githubusercontent.com/gbiz123/sadcaptcha-code-examples/master/images/outer_tt.png")
|
|
14
|
+
res = api_client.rotate(outer, inner)
|
|
15
|
+
assert isinstance(res, RotateCaptchaResponse)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_puzzle():
|
|
19
|
+
piece = download_image_b64("https://raw.githubusercontent.com/gbiz123/sadcaptcha-code-examples/master/images/piece.png")
|
|
20
|
+
puzzle = download_image_b64("https://raw.githubusercontent.com/gbiz123/sadcaptcha-code-examples/master/images/puzzle.jpg")
|
|
21
|
+
res = api_client.puzzle(puzzle, piece)
|
|
22
|
+
assert isinstance(res, PuzzleCaptchaResponse)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_shapes():
|
|
26
|
+
image = download_image_b64("https://raw.githubusercontent.com/gbiz123/sadcaptcha-code-examples/master/images/tiktok3d.png")
|
|
27
|
+
res = api_client.shapes(image)
|
|
28
|
+
assert isinstance(res, ShapesCaptchaResponse)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from webdriver_manager.chrome import ChromeDriverManager
|
|
5
|
+
from selenium import webdriver
|
|
6
|
+
from selenium.webdriver.common.by import By
|
|
7
|
+
import undetected_chromedriver as uc
|
|
8
|
+
|
|
9
|
+
from ..sadcaptcha import SadCaptcha
|
|
10
|
+
|
|
11
|
+
options = webdriver.ChromeOptions()
|
|
12
|
+
options.add_argument('--no-sandbox')
|
|
13
|
+
options.add_argument('--disable-infobars')
|
|
14
|
+
options.add_argument('--disable-gpu')
|
|
15
|
+
options.add_argument('--disable-dev-shm-usage')
|
|
16
|
+
options.add_argument('--disable-blink-features=AutomationControlled')
|
|
17
|
+
options.add_experimental_option('excludeSwitches', ['enable-automation'])
|
|
18
|
+
options.add_experimental_option('useAutomationExtension', False)
|
|
19
|
+
options.add_argument("start-maximized")
|
|
20
|
+
options.add_argument(
|
|
21
|
+
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36")
|
|
22
|
+
options.add_argument("--accept-lang=en-US,en;q=0.5")
|
|
23
|
+
options.add_argument("--dom-automation=disabled")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def make_driver() -> uc.Chrome:
|
|
28
|
+
return uc.Chrome(service=ChromeDriverManager().install(), headless=False, use_subprocess=False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def open_tiktkok_login(driver: uc.Chrome) -> None:
|
|
32
|
+
driver.get("https://www.tiktok.com/login/phone-or-email/email")
|
|
33
|
+
time.sleep(10)
|
|
34
|
+
write_username = driver.find_element(By.XPATH, '//input[contains(@name,"username")]');
|
|
35
|
+
write_username.send_keys("greg@toughdata.net");
|
|
36
|
+
time.sleep(2);
|
|
37
|
+
write_password = driver.find_element(By.XPATH, '//input[contains(@type,"password")]');
|
|
38
|
+
write_password.send_keys("th.etoughapi1!");
|
|
39
|
+
time.sleep(2)
|
|
40
|
+
login_btn = driver.find_element(By.XPATH, '//button[contains(@data-e2e,"login-button")]').click();
|
|
41
|
+
time.sleep(8)
|
|
42
|
+
|
|
43
|
+
def test_solve_captcha():
|
|
44
|
+
driver = make_driver()
|
|
45
|
+
open_tiktkok_login(driver)
|
|
46
|
+
sadcaptcha = SadCaptcha(driver, os.environ["API_KEY"])
|
|
47
|
+
sadcaptcha.solve_captcha_if_present()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tiktok-captcha-solver
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: This package integrates with Selenium to solve any TikTok captcha in one line of code.
|
|
5
|
+
Author-email: Toughdata LLC <greg@toughdata.net>
|
|
6
|
+
Project-URL: Homepage, https://www.sadcaptcha.com
|
|
7
|
+
Project-URL: Source, https://github.com/gbiz123/tiktok-captcha-solver/
|
|
8
|
+
Keywords: tiktok,captcha,solver,selenium,rotate,puzzle,3d
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: selenium
|
|
18
|
+
Requires-Dist: webdriver-manager
|
|
19
|
+
Requires-Dist: pydantic
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: pytest
|
|
22
|
+
Requires-Dist: undetected_chromedriver
|
|
23
|
+
|
|
24
|
+
# TikTok Captcha Solver API
|
|
25
|
+
This project is the [SadCaptcha TikTok Captcha Solver](https://www.sadcaptcha.com?ref=ghclientrepo) API client.
|
|
26
|
+
The purpose is to make integrating SadCaptcha into your selenium app as simple as one line of code.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
- Python >= 3.10
|
|
30
|
+
- Selenium properly installed and in `PATH`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
This project will be available as a `pip` package soon.
|
|
34
|
+
For now, just clone the repo and build it using the following commands:
|
|
35
|
+
```
|
|
36
|
+
git clone https://github.com/gbiz123/tiktok-captcha-solver
|
|
37
|
+
cd tiktok-captcha-solver
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
Import the package, set up the SadCaptcha class, and call it whenever you need.
|
|
43
|
+
|
|
44
|
+
```py
|
|
45
|
+
from sadcaptcha import SadCaptcha
|
|
46
|
+
import undetected_chromedriver as uc
|
|
47
|
+
|
|
48
|
+
driver = uc.Chrome(headless=False)
|
|
49
|
+
api_key = "YOUR_API_KEY_HERE"
|
|
50
|
+
sadcaptcha = SadCaptcha(driver, api_key)
|
|
51
|
+
|
|
52
|
+
# Selenium code that causes a TikTok captcha...
|
|
53
|
+
|
|
54
|
+
sadcaptcha.solve_captcha_if_present()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it!
|
|
58
|
+
|
|
59
|
+
## Contact
|
|
60
|
+
- Homepage: https://www.sadcaptcha.com/
|
|
61
|
+
- Email: info@toughdata.net
|
|
62
|
+
- Telegram @toughdata
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
tiktok_captcha_solver/__init__.py
|
|
4
|
+
tiktok_captcha_solver/api.py
|
|
5
|
+
tiktok_captcha_solver/downloader.py
|
|
6
|
+
tiktok_captcha_solver/models.py
|
|
7
|
+
tiktok_captcha_solver/sadcaptcha.py
|
|
8
|
+
tiktok_captcha_solver.egg-info/PKG-INFO
|
|
9
|
+
tiktok_captcha_solver.egg-info/SOURCES.txt
|
|
10
|
+
tiktok_captcha_solver.egg-info/dependency_links.txt
|
|
11
|
+
tiktok_captcha_solver.egg-info/requires.txt
|
|
12
|
+
tiktok_captcha_solver.egg-info/top_level.txt
|
|
13
|
+
tiktok_captcha_solver/tests/__init__.py
|
|
14
|
+
tiktok_captcha_solver/tests/test_api.py
|
|
15
|
+
tiktok_captcha_solver/tests/test_downloader.py
|
|
16
|
+
tiktok_captcha_solver/tests/test_sadcaptcha.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tiktok_captcha_solver
|