First commit
This commit is contained in:
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/.venv
|
||||||
|
/.vscode
|
||||||
|
__pycache__
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Pablo Ferreiro
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# SignTok
|
||||||
|
Sign your TikTok urls easily.
|
||||||
|
This is a port of [tiktok-signature](https://github.com/carcabot/tiktok-signature) to Python using Selenium and Selenium Stealth
|
||||||
|
|
||||||
|
[](https://heroku.com/deploy)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
You also need to have Chrome installed
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
For webserver:
|
||||||
|
```
|
||||||
|
python web.py
|
||||||
|
```
|
||||||
|
Then you can send a POST request to localhost:8080 with a raw/plain body containing the url
|
||||||
|
|
||||||
|
For cli usage:
|
||||||
|
```
|
||||||
|
python cli.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
* Docker
|
||||||
83
TikTokSigner/Signer.py
Normal file
83
TikTokSigner/Signer.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
from os import getenv
|
||||||
|
from time import sleep
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium_stealth import stealth
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from urllib import parse
|
||||||
|
from TikTokSigner.Utils import Utils
|
||||||
|
|
||||||
|
class Signer:
|
||||||
|
DEFAULT_URL = 'https://www.tiktok.com/@tiktok/?lang=en'
|
||||||
|
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Windows NT 10.0; Win64; x64) Chrome/90.0.4430.85 Safari/537.36'
|
||||||
|
driver: webdriver.Chrome
|
||||||
|
|
||||||
|
signature = ''
|
||||||
|
xttparams = ''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
options = Options()
|
||||||
|
path = getenv('GOOGLE_CHROME_BIN', '')
|
||||||
|
options._binary_location = path
|
||||||
|
options.add_argument("start-maximized")
|
||||||
|
options.add_argument("--headless")
|
||||||
|
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||||
|
options.add_experimental_option('useAutomationExtension', False)
|
||||||
|
options.add_argument('--disable-blink-features=AutomationControlled')
|
||||||
|
self.driver = webdriver.Chrome(options=options)
|
||||||
|
stealth(self.driver,
|
||||||
|
user_agent=self.USER_AGENT,
|
||||||
|
languages=["en-US", "en"],
|
||||||
|
vendor="Google Inc.",
|
||||||
|
platform="Win32",
|
||||||
|
webgl_vendor="Intel Inc.",
|
||||||
|
renderer="Intel Iris OpenGL Engine",
|
||||||
|
fix_hairline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.driver.get(self.DEFAULT_URL)
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
# Load scripts
|
||||||
|
with open('./js/signature.js') as f:
|
||||||
|
signature = f.read()
|
||||||
|
|
||||||
|
with open('./js/xttparams.js') as f:
|
||||||
|
xttparams = f.read()
|
||||||
|
|
||||||
|
self.driver.execute_script(signature)
|
||||||
|
self.driver.execute_script(xttparams)
|
||||||
|
|
||||||
|
def navigator(self)-> dict:
|
||||||
|
info = self.driver.execute_script("""
|
||||||
|
return {
|
||||||
|
deviceScaleFactor: window.devicePixelRatio,
|
||||||
|
user_agent: window.navigator.userAgent,
|
||||||
|
browser_language: window.navigator.language,
|
||||||
|
browser_platform: window.navigator.platform,
|
||||||
|
browser_name: window.navigator.appCodeName,
|
||||||
|
browser_version: window.navigator.appVersion,
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
return info
|
||||||
|
|
||||||
|
def sign(self, url: str)-> dict:
|
||||||
|
fp = Utils.verify_fp()
|
||||||
|
# Add verifyFp to url
|
||||||
|
url += '&verifyFp=' + fp
|
||||||
|
signature = self.driver.execute_script('return window.byted_acrawler.sign(arguments[0])', {
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
signed_url = url + '&_signature=' + signature
|
||||||
|
|
||||||
|
# Get params of url as dict
|
||||||
|
params = dict(parse.parse_qsl(parse.urlsplit(url).query))
|
||||||
|
xttparams = self.driver.execute_script('return window.genXTTParams(arguments[0])', params)
|
||||||
|
return {
|
||||||
|
'signature': signature,
|
||||||
|
'verify_fp': fp,
|
||||||
|
'signed_url': signed_url,
|
||||||
|
'x-tt-params': xttparams
|
||||||
|
}
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.driver.close()
|
||||||
9
TikTokSigner/Utils.py
Normal file
9
TikTokSigner/Utils.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import time
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
class Utils:
|
||||||
|
@staticmethod
|
||||||
|
def verify_fp()-> str:
|
||||||
|
# TODO, add proper verify fp method
|
||||||
|
return 'verify_5b161567bda98b6a50c0414d99909d4b'
|
||||||
0
TikTokSigner/__init__.py
Normal file
0
TikTokSigner/__init__.py
Normal file
22
app.json
Normal file
22
app.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "SignTok",
|
||||||
|
"description": "Sign TikTok requests",
|
||||||
|
"keywords": [
|
||||||
|
"tiktok",
|
||||||
|
"api",
|
||||||
|
"python"
|
||||||
|
],
|
||||||
|
"website": "https://github.com/pablouser1/SignTok",
|
||||||
|
"repository": "https://github.com/pablouser1/SignTok",
|
||||||
|
"buildpacks": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/heroku/heroku-buildpack-google-chrome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/heroku/heroku-buildpack-chromedriver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "heroku/python"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
cli.py
Normal file
18
cli.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from TikTokSigner.Signer import Signer
|
||||||
|
from sys import argv
|
||||||
|
import json
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(argv) > 1:
|
||||||
|
signer = Signer()
|
||||||
|
data = signer.sign(argv[1])
|
||||||
|
nav = signer.navigator()
|
||||||
|
res = {
|
||||||
|
'status': 'ok',
|
||||||
|
'data': data,
|
||||||
|
'navigator': nav
|
||||||
|
}
|
||||||
|
print(json.dumps(res))
|
||||||
|
signer.cleanup()
|
||||||
|
else:
|
||||||
|
print('You need to type a url!')
|
||||||
1
js/signature.js
Normal file
1
js/signature.js
Normal file
File diff suppressed because one or more lines are too long
2
js/xttparams.js
Normal file
2
js/xttparams.js
Normal file
File diff suppressed because one or more lines are too long
19
requirements.txt
Normal file
19
requirements.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
async-generator==1.10
|
||||||
|
attrs==21.4.0
|
||||||
|
certifi==2021.10.8
|
||||||
|
cffi==1.15.0
|
||||||
|
cryptography==36.0.1
|
||||||
|
h11==0.13.0
|
||||||
|
idna==3.3
|
||||||
|
outcome==1.1.0
|
||||||
|
pycparser==2.21
|
||||||
|
pyOpenSSL==22.0.0
|
||||||
|
PySocks==1.7.1
|
||||||
|
selenium==4.1.2
|
||||||
|
selenium-stealth==1.0.6
|
||||||
|
sniffio==1.2.0
|
||||||
|
sortedcontainers==2.4.0
|
||||||
|
trio==0.20.0
|
||||||
|
trio-websocket==0.9.2
|
||||||
|
urllib3==1.26.8
|
||||||
|
wsproto==1.1.0
|
||||||
35
web.py
Normal file
35
web.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import json
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
from TikTokSigner.Signer import Signer
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
signer = Signer()
|
||||||
|
|
||||||
|
class TikServer(BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
content_length = int(self.headers['Content-Length'])
|
||||||
|
post_data = self.rfile.read(content_length)
|
||||||
|
url = post_data.decode()
|
||||||
|
|
||||||
|
data = signer.sign(url)
|
||||||
|
nav = signer.navigator()
|
||||||
|
|
||||||
|
res = {
|
||||||
|
'status': 'ok',
|
||||||
|
'data': data,
|
||||||
|
'navigator': nav
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(json.dumps(res).encode())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
PORT = getenv('PORT', 8080)
|
||||||
|
try:
|
||||||
|
print('Starting server')
|
||||||
|
httpd = HTTPServer(('0.0.0.0', PORT), TikServer)
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
httpd.shutdown()
|
||||||
Reference in New Issue
Block a user