Moved to JS, using JSDOM and added Docker
This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
@@ -11,6 +11,6 @@ trim_trailing_whitespace = true
|
|||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
# 4 space indentation
|
# 4 space indentation
|
||||||
[*.py]
|
[*.js]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
/.venv
|
/.venv
|
||||||
/.vscode
|
/.vscode
|
||||||
__pycache__
|
/node_modules
|
||||||
|
|||||||
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM node:17
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package.json ./
|
||||||
|
RUN yarn install
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD [ "node", "server.js" ]
|
||||||
13
README.md
13
README.md
@@ -1,26 +1,21 @@
|
|||||||
# SignTok
|
# SignTok
|
||||||
Sign your TikTok urls easily.
|
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
|
This is a port of [tiktok-signature](https://github.com/carcabot/tiktok-signature) using JSDOM
|
||||||
|
|
||||||
[](https://heroku.com/deploy)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
```
|
```
|
||||||
pip install -r requirements.txt
|
yarn install
|
||||||
```
|
```
|
||||||
You also need to have Chrome installed
|
You also need to have Chrome installed
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
For webserver:
|
For webserver:
|
||||||
```
|
```
|
||||||
python web.py
|
node server.py
|
||||||
```
|
```
|
||||||
Then you can send a POST request to localhost:8080 with a raw/plain body containing the url
|
Then you can send a POST request to localhost:8080 with a raw/plain body containing the url
|
||||||
|
|
||||||
For cli usage:
|
For cli usage:
|
||||||
```
|
```
|
||||||
python cli.py
|
node index.js 'YOUR_URL_HERE'
|
||||||
```
|
```
|
||||||
|
|
||||||
## TODO
|
|
||||||
* Docker
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
from os import getenv
|
|
||||||
from selenium import webdriver
|
|
||||||
from selenium_stealth import stealth
|
|
||||||
from selenium.webdriver.chrome.options import Options
|
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
from selenium.webdriver.common.by import By
|
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
|
||||||
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'
|
|
||||||
TIMEOUT = 30
|
|
||||||
driver: webdriver.Chrome
|
|
||||||
|
|
||||||
signature = ''
|
|
||||||
xttparams = ''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
print('Starting Browser')
|
|
||||||
options = Options()
|
|
||||||
path = getenv('GOOGLE_CHROME_SHIM', '')
|
|
||||||
options._binary_location = path
|
|
||||||
options.add_argument("--headless")
|
|
||||||
options.add_argument("--disable-gpu")
|
|
||||||
options.add_argument("--no-sandbox")
|
|
||||||
options.add_argument("--remote-debugging-port=9222")
|
|
||||||
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)
|
|
||||||
WebDriverWait(self.driver, self.TIMEOUT).until(EC.presence_of_element_located((By.ID, 'app')))
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import time
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
|
|
||||||
class Utils:
|
|
||||||
@staticmethod
|
|
||||||
def verify_fp()-> str:
|
|
||||||
# TODO, add proper verify fp method
|
|
||||||
return 'verify_5b161567bda98b6a50c0414d99909d4b'
|
|
||||||
10
app.json
10
app.json
@@ -4,19 +4,13 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"tiktok",
|
"tiktok",
|
||||||
"api",
|
"api",
|
||||||
"python"
|
"node"
|
||||||
],
|
],
|
||||||
"website": "https://github.com/pablouser1/SignTok",
|
"website": "https://github.com/pablouser1/SignTok",
|
||||||
"repository": "https://github.com/pablouser1/SignTok",
|
"repository": "https://github.com/pablouser1/SignTok",
|
||||||
"buildpacks": [
|
"buildpacks": [
|
||||||
{
|
{
|
||||||
"url": "https://github.com/heroku/heroku-buildpack-google-chrome"
|
"url": "heroku/nodejs"
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/heroku/heroku-buildpack-chromedriver"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "heroku/python"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
20
cli.py
20
cli.py
@@ -1,20 +0,0 @@
|
|||||||
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!')
|
|
||||||
9
index.js
Normal file
9
index.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const Signer = require("./src/Signer")
|
||||||
|
|
||||||
|
if (process.argv.length > 2) {
|
||||||
|
const signer = new Signer()
|
||||||
|
const url = process.argv[2]
|
||||||
|
|
||||||
|
const data = signer.sign(url)
|
||||||
|
console.log(data)
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "signtok",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Sign your TikTok requests easily",
|
||||||
|
"repository": "https://github.com/pablouser1/SignTok",
|
||||||
|
"author": "Pablo Ferreiro",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"canvas": "^2.9.1",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
|
"express": "^4.17.3",
|
||||||
|
"jsdom": "^19.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
30
server.js
Normal file
30
server.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const Signer = require("./src/Signer")
|
||||||
|
const bodyParser = require("body-parser")
|
||||||
|
const express = require("express")
|
||||||
|
|
||||||
|
const port = process.env.PORT || 8080
|
||||||
|
const app = express()
|
||||||
|
app.use(bodyParser.text())
|
||||||
|
|
||||||
|
const signer = new Signer()
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.redirect('https://github.com/pablouser1/SignTok')
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/signature", (req, res) => {
|
||||||
|
const url = req.body
|
||||||
|
const data = signer.sign(url)
|
||||||
|
console.log("Sent data from request with url: " + url)
|
||||||
|
res.send({
|
||||||
|
status: "ok",
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
navigator: signer.navigator()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`App listening on port ${port}`)
|
||||||
|
})
|
||||||
78
src/Signer.js
Normal file
78
src/Signer.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const fs = require("fs")
|
||||||
|
const Utils = require("./Utils")
|
||||||
|
const { JSDOM, ResourceLoader } = require("jsdom")
|
||||||
|
const CryptoJS = require("crypto-js")
|
||||||
|
|
||||||
|
class Signer {
|
||||||
|
static DEFAULT_USERAGENT =
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36"
|
||||||
|
static PASSWORD = CryptoJS.enc.Utf8.parse('webapp1.0+202106')
|
||||||
|
/**
|
||||||
|
* @type Window
|
||||||
|
*/
|
||||||
|
window = null
|
||||||
|
|
||||||
|
constructor(userAgent = Signer.DEFAULT_USERAGENT) {
|
||||||
|
const signature_js = fs.readFileSync(__dirname + "/../js/signature.js", "utf-8")
|
||||||
|
const resourceLoader = new ResourceLoader({
|
||||||
|
userAgent
|
||||||
|
})
|
||||||
|
|
||||||
|
const { window } = new JSDOM(``, {
|
||||||
|
url: "https://www.tiktok.com",
|
||||||
|
referrer: "https://www.tiktok.com",
|
||||||
|
contentType: "text/html",
|
||||||
|
includeNodeLocations: true,
|
||||||
|
runScripts: "outside-only",
|
||||||
|
resources: resourceLoader
|
||||||
|
})
|
||||||
|
this.window = window
|
||||||
|
this.window.eval(signature_js.toString())
|
||||||
|
this.window.byted_acrawler.init({
|
||||||
|
"aid":24,
|
||||||
|
"dfp":true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator() {
|
||||||
|
return {
|
||||||
|
deviceScaleFactor: this.window.devicePixelRatio,
|
||||||
|
user_agent: this.window.navigator.userAgent,
|
||||||
|
browser_language: this.window.navigator.language,
|
||||||
|
browser_platform: this.window.navigator.platform,
|
||||||
|
browser_name: this.window.navigator.appCodeName,
|
||||||
|
browser_version: this.window.navigator.appVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature(url) {
|
||||||
|
return this.window.byted_acrawler.sign({ url })
|
||||||
|
}
|
||||||
|
|
||||||
|
xttparams(params) {
|
||||||
|
params += "&is_encryption=1"
|
||||||
|
const crypt = CryptoJS.AES.encrypt(params, Signer.PASSWORD, {
|
||||||
|
iv: Signer.PASSWORD,
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7
|
||||||
|
}).toString()
|
||||||
|
return crypt
|
||||||
|
}
|
||||||
|
|
||||||
|
sign(url) {
|
||||||
|
const verifyFp = Utils.verify_fp()
|
||||||
|
url += "&verifyFp=" + verifyFp
|
||||||
|
const signature = this.signature(url)
|
||||||
|
const signed_url = url + "&_signature=" + signature
|
||||||
|
const params = new URL(url).searchParams.toString()
|
||||||
|
const xttparams = this.xttparams(params)
|
||||||
|
return {
|
||||||
|
signature: signature,
|
||||||
|
verify_fp: verifyFp,
|
||||||
|
signed_url: signed_url,
|
||||||
|
"x-tt-params": xttparams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Signer
|
||||||
8
src/Utils.js
Normal file
8
src/Utils.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
class Utils {
|
||||||
|
static verify_fp() {
|
||||||
|
// TODO, add proper verify fp method
|
||||||
|
return "verify_68b8ccfa65726db8b3db0cc07821d696"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Utils
|
||||||
38
web.py
38
web.py
@@ -1,38 +0,0 @@
|
|||||||
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):
|
|
||||||
if self.path == '/signature':
|
|
||||||
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