Moved to JS, using JSDOM and added Docker

This commit is contained in:
Pablo Ferreiro
2022-04-17 22:30:27 +02:00
parent 912e09882f
commit 6980abbef9
20 changed files with 1274 additions and 198 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -11,6 +11,6 @@ trim_trailing_whitespace = true
charset = utf-8
# 4 space indentation
[*.py]
[*.js]
indent_style = space
indent_size = 4
indent_size = 2

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
/.venv
/.vscode
__pycache__
/node_modules

9
Dockerfile Normal file
View 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" ]

View File

@@ -1,26 +1,21 @@
# 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
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
This is a port of [tiktok-signature](https://github.com/carcabot/tiktok-signature) using JSDOM
## Installation
```
pip install -r requirements.txt
yarn install
```
You also need to have Chrome installed
## Usage
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
For cli usage:
```
python cli.py
node index.js 'YOUR_URL_HERE'
```
## TODO
* Docker

View File

@@ -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()

View File

@@ -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'

View File

@@ -4,19 +4,13 @@
"keywords": [
"tiktok",
"api",
"python"
"node"
],
"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"
"url": "heroku/nodejs"
}
]
}

20
cli.py
View File

@@ -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
View 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
View 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"
}
}

View File

@@ -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
View 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
View 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
View 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
View File

@@ -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()

1111
yarn.lock Normal file

File diff suppressed because it is too large Load Diff