Add Douyin video downloader with auto-cookie feature
This commit is contained in:
197
Server/Server.py
Normal file
197
Server/Server.py
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
'''
|
||||
@Description:Server.py
|
||||
@Date :2023/02/25 17:03:32
|
||||
@Author :JohnserfSeed
|
||||
@version :0.0.1
|
||||
@License :MIT License
|
||||
@Github :https://github.com/johnserf-seed
|
||||
@Mail :johnserf-seed@foxmail.com
|
||||
-------------------------------------------------
|
||||
Change Log :
|
||||
2023/02/25 17:03:32 - Create Flask Server XB Gen
|
||||
2023/08/03 16:48:34 - Fix ttwid
|
||||
-------------------------------------------------
|
||||
'''
|
||||
|
||||
import time
|
||||
import execjs
|
||||
# import sqlite3
|
||||
import requests
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import jsonify
|
||||
# from flask import make_response
|
||||
# from flask import render_template
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import unquote
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
class Server:
|
||||
def __init__(self) -> None:
|
||||
# 工厂模式
|
||||
self.app = Flask(__name__)
|
||||
|
||||
self.app.config.from_mapping(
|
||||
SECRET_KEY='douyin-xbogus'
|
||||
)
|
||||
|
||||
self.app.config['JSON_AS_ASCII'] = False
|
||||
|
||||
with open("x-bogus.js", "r", encoding="utf-8") as fp:
|
||||
self.xbogust_func = execjs.compile(fp.read())
|
||||
|
||||
with open("x-tt-params.js", "r", encoding="utf-8") as fp:
|
||||
self.xttm_func = execjs.compile(fp.read())
|
||||
|
||||
self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
|
||||
|
||||
# 获取xg参数
|
||||
def getXG(self, url_path, params):
|
||||
xbogus = self.xbogust_func.call("getXB", url_path)
|
||||
# 字典中添加xg
|
||||
params["X-Bogus"] = xbogus
|
||||
tips = {
|
||||
"status_code": "200",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"result": [{
|
||||
"params": params,
|
||||
"paramsencode": urlencode(params, safe="="),
|
||||
"user-agent": self.ua,
|
||||
"X-Bogus": {
|
||||
0: xbogus,
|
||||
1: "X-Bogus=%s" % xbogus
|
||||
}
|
||||
}]
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
|
||||
# 生成x-tt-params
|
||||
def getxttparams(self, url_path):
|
||||
xttp = self.xttm_func.call("getXTTP", url_path)
|
||||
tips = {
|
||||
"status_code": "200",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"result": [{
|
||||
"headers": {
|
||||
"user-agent": self.ua,
|
||||
"x-tt-params": xttp
|
||||
}
|
||||
}]
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
|
||||
def gen_ttwid(self) -> str:
|
||||
"""生成请求必带的ttwid
|
||||
param :None
|
||||
return:ttwid
|
||||
"""
|
||||
url = 'https://ttwid.bytedance.com/ttwid/union/register/'
|
||||
data = '{"region":"cn","aid":1768,"needFid":false,"service":"www.ixigua.com","migrate_info":{"ticket":"","source":"node"},"cbUrlProtocol":"https","union":true}'
|
||||
response = requests.request("POST", url, data=data)
|
||||
# j = ttwid k = 1%7CfPx9ZM.....
|
||||
for j, k in response.cookies.items():
|
||||
tips = {
|
||||
"status_code": "200",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"result": [{
|
||||
"headers": {
|
||||
"user-agent": self.ua,
|
||||
"cookie": "ttwid=%s;" % k
|
||||
}
|
||||
}]
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = Server()
|
||||
# 首页
|
||||
@server.app.route('/', methods=['GET', 'POST'])
|
||||
def index():
|
||||
tips = {
|
||||
"status_code": "-1",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"path": {
|
||||
0: "/xg/path/?url=",
|
||||
2: "/x-tt-params/path"
|
||||
}
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
|
||||
# xg参数
|
||||
@server.app.route('/xg/path/', methods=['GET', 'POST'])
|
||||
def xgpath():
|
||||
path = request.args.get('url', '')
|
||||
# 如果str路径为空
|
||||
if not path:
|
||||
tips = {
|
||||
"status_code": "-3",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"message": {
|
||||
0: "The key url cannot be empty and the need for url encoding, The '&' sign needs to be escaped to '%26', Use urllib.parse.quote(url) to escape. Example:/xg/path/?url=aid=6383%26sec_user_id=xxx%26max_cursor=0%26count=10",
|
||||
1: "url参数不能为空,且需要注意传入值中的“&”需要转义成“%26”,使用urllib.parse.quote(url)转义. 例如:/xg/path/?url=aid=6383%26sec_user_id=xxx%26max_cursor=0%26count=10"
|
||||
}
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
else:
|
||||
# url转字典
|
||||
params = dict(parse_qsl(path))
|
||||
# 字典转url
|
||||
url_path = urlencode(params, safe="=")
|
||||
return server.getXG(url_path, params)
|
||||
|
||||
# x-tt-params参数
|
||||
@server.app.route('/x-tt-params/path', methods=['GET', 'POST'])
|
||||
def xttppath():
|
||||
try:
|
||||
path = request.json
|
||||
except:
|
||||
pass
|
||||
if not path:
|
||||
tips = {
|
||||
"status_code": "-5",
|
||||
"time": {
|
||||
"strftime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
||||
"timestamp": int(round(time.time() * 1000))
|
||||
},
|
||||
"message": {
|
||||
0: "Body uses raw JSON format to pass dictionary parameters, such as %s" % '{"aid": 1988,"app_name": "tiktok_web","channel": "tiktok_web".........}',
|
||||
1: "body中使用raw json格式传递字典参数,如%s" % '{"aid": 1988,"app_name": "tiktok_web","channel": "tiktok_web".........}'
|
||||
}
|
||||
}
|
||||
print(tips)
|
||||
return jsonify(tips)
|
||||
else:
|
||||
return server.getxttparams(path)
|
||||
|
||||
# ttwid
|
||||
@server.app.route('/xg/ttwid', methods=['GET', 'POST'])
|
||||
def ttwid():
|
||||
return server.gen_ttwid()
|
||||
|
||||
|
||||
server.app.run(host='0.0.0.0',port='8889')
|
||||
41
Server/Server.txt
Normal file
41
Server/Server.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
# UTF-8
|
||||
#
|
||||
# For more details about fixed file info 'ffi' see:
|
||||
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0.
|
||||
filevers=(1, 4, 2, 2),
|
||||
prodvers=(1, 4, 2, 2),
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3f,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
flags=0x0,
|
||||
# The operating system for which this file was designed.
|
||||
# 0x4 - NT and there is no need to change it.
|
||||
OS=0x40004,
|
||||
# The general type of file.
|
||||
# 0x1 - the file is an application.
|
||||
fileType=0x1,
|
||||
# The function of the file.
|
||||
# 0x0 - the function is not defined for this fileType
|
||||
subtype=0x0,
|
||||
# Creation date and time stamp.
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo(
|
||||
[
|
||||
StringTable(
|
||||
u'080404b0',
|
||||
[StringStruct(u'CompanyName', u'JohnserfSeed'),
|
||||
StringStruct(u'FileDescription', u'本地解析服务'),
|
||||
StringStruct(u'FileVersion', u'1.5.0.0'),
|
||||
StringStruct(u'LegalCopyright', u'Copyright (C) 2019-2023 JohnserfSeed. All Rights Reserved'),
|
||||
StringStruct(u'ProductName', u'本地解析服务'),
|
||||
StringStruct(u'ProductVersion', u'1.5.0.0')])
|
||||
]),
|
||||
VarFileInfo([VarStruct(u'Translation', [2052, 1200])])
|
||||
]
|
||||
)
|
||||
9
Server/build-win.bat
Normal file
9
Server/build-win.bat
Normal file
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
echo Install Pip Require
|
||||
pip install -r requirements.txt
|
||||
echo Build EXE version, Press Ctrl + C to Exit
|
||||
echo Build Server
|
||||
pyinstaller -F -i ..\f2-logo.ico --distpath . --version-file Server.txt --hidden-import=charset_normalizer.md__mypyc Server.py
|
||||
echo Install Npm Require
|
||||
npm i
|
||||
pause
|
||||
6
Server/package.json
Normal file
6
Server/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.1.1",
|
||||
"md5": "^2.3.0"
|
||||
}
|
||||
}
|
||||
2
Server/requirements.txt
Normal file
2
Server/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Flask==2.2.5
|
||||
PyExecJS==1.5.1
|
||||
15
Server/s_v_web_id.js
Normal file
15
Server/s_v_web_id.js
Normal file
@@ -0,0 +1,15 @@
|
||||
function create_s_v_web_id() {
|
||||
var e = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split("")
|
||||
, t = e.length
|
||||
, n = (new Date).getTime().toString(36)
|
||||
, r = [];
|
||||
|
||||
r[8] = r[13] = r[18] = r[23] = "_",
|
||||
r[14] = "4";
|
||||
for (var o, i = 0; i < 36; i++)
|
||||
r[i] || (o = 0 | Math.random() * t,
|
||||
r[i] = e[19 == i ? 3 & o | 8 : o]);
|
||||
return "verify_" + n + "_" + r.join("")
|
||||
}
|
||||
|
||||
console.log(create_s_v_web_id())
|
||||
31
Server/s_v_web_id.py
Normal file
31
Server/s_v_web_id.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import time
|
||||
import random
|
||||
|
||||
def create_s_v_web_id():
|
||||
e = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
t = len(e)
|
||||
n = base36_encode(int(time.time()*1000)) # Convert timestamp to base 36
|
||||
|
||||
r = [''] * 36
|
||||
r[8] = r[13] = r[18] = r[23] = "_"
|
||||
r[14] = "4"
|
||||
|
||||
for i in range(36):
|
||||
if not r[i]:
|
||||
o = int(random.random() * t)
|
||||
r[i] = e[3 & o | 8 if i == 19 else o]
|
||||
|
||||
return "verify_" + n + "_" + "".join(r)
|
||||
|
||||
def base36_encode(number):
|
||||
"""Converts an integer to a base36 string."""
|
||||
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
base36 = []
|
||||
|
||||
while number:
|
||||
number, i = divmod(number, 36)
|
||||
base36.append(alphabet[i])
|
||||
|
||||
return ''.join(reversed(base36))
|
||||
|
||||
print(create_s_v_web_id())
|
||||
132
Server/x-bogus.js
Normal file
132
Server/x-bogus.js
Normal file
@@ -0,0 +1,132 @@
|
||||
let MD5 = require("md5");
|
||||
|
||||
let Array = [ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 10, 11, 12, 13, 14, 15 ];
|
||||
|
||||
// let _0x4129ad = 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe';
|
||||
// let _0x127ecb = '=';
|
||||
let _0x377d66 = "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=";
|
||||
|
||||
function _0x39ced2(l) {
|
||||
let n = [];
|
||||
for (let u = 0; u < l.length; ) {
|
||||
n.push(Array[l.charCodeAt(u++)] << 4 | Array[l.charCodeAt(u++)]);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function _0x1da120(l) {
|
||||
return _0x39ced2(MD5(_0x39ced2(MD5(l))));
|
||||
}
|
||||
|
||||
function _0x2efd11(l) {
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(l);
|
||||
}
|
||||
|
||||
function _0x2d9dba(l) {
|
||||
var n, u, e, t, r, o = "";
|
||||
for (n = 0; n < l.length - 3; n += 4) {
|
||||
u = _0x2efd11(l.charAt(n)), e = _0x2efd11(l.charAt(n + 1)), t = _0x2efd11(l.charAt(n + 2)),
|
||||
r = _0x2efd11(l.charAt(n + 3)), o += String.fromCharCode(u << 2 | e >>> 4), "=" !== l.charAt(n + 2) && (o += String.fromCharCode(e << 4 & 240 | t >>> 2 & 15)),
|
||||
"=" !== l.charAt(n + 3) && (o += String.fromCharCode(t << 6 & 192 | r));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
function _0x24e7c9() {
|
||||
var l = "";
|
||||
try {
|
||||
window.sessionStorage && (l = window.sessionStorage.getItem("_byted_param_sw")),
|
||||
l && !window.localStorage || (l = window.localStorage.getItem("_byted_param_sw"));
|
||||
} catch (l) {}
|
||||
if (l) {
|
||||
try {
|
||||
var n = _0x3459bb(_0x2d9dba(l.slice(8)), l.slice(0, 8));
|
||||
if ("on" === n) {
|
||||
return !0;
|
||||
}
|
||||
if ("off" === n) {
|
||||
return !1;
|
||||
}
|
||||
} catch (l) {}
|
||||
}
|
||||
return !1;
|
||||
}
|
||||
|
||||
function _0x4d54ed(l) {
|
||||
try {
|
||||
return window.localStorage ? window.localStorage.getItem(l) : null;
|
||||
} catch (l) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _0x478bb3(l, n, u) {
|
||||
let e = (255 & l) << 16;
|
||||
let t = (255 & n) << 8;
|
||||
let r = e | t | u;
|
||||
return _0x377d66[(16515072 & r) >> 18] + _0x377d66[(258048 & r) >> 12] + _0x377d66[(4032 & r) >> 6] + _0x377d66[63 & r];
|
||||
}
|
||||
|
||||
function _0x481826(l) {
|
||||
void 0 !== l && "" != l && (_0x402a35.ttwid = l);
|
||||
}
|
||||
|
||||
function _0x37f15d() {
|
||||
var l = _0x4d54ed("xmst");
|
||||
return l || "";
|
||||
}
|
||||
|
||||
function _0x330d11(l, n, u, e, t, r, o, d, a, c, i, f, x, _, h, g, C, s, p) {
|
||||
let w = new Uint8Array(19);
|
||||
w[0] = l, w[1] = i, w[2] = n, w[3] = f, w[4] = u, w[5] = x, w[6] = e, w[7] = _,
|
||||
w[8] = t, w[9] = h, w[10] = r, w[11] = g, w[12] = o, w[13] = C, w[14] = d, w[15] = s,
|
||||
w[16] = a, w[17] = p, w[18] = c;
|
||||
return String.fromCharCode.apply(null, w);
|
||||
}
|
||||
|
||||
function _0x330d112(l, n) {
|
||||
let u, e = [], t = 0, r = "", o = 0, d = 0, a = 0;
|
||||
for (let l = 0; l < 256; l++) {
|
||||
e[l] = l;
|
||||
}
|
||||
for (;o < 256; o++) {
|
||||
t = (t + e[o] + l.charCodeAt(o % l.length)) % 256, u = e[o], e[o] = e[t], e[t] = u;
|
||||
}
|
||||
t = 0;
|
||||
for (;d < n.length; d++) {
|
||||
t = (t + e[a = (a + 1) % 256]) % 256, u = e[a], e[a] = e[t], e[t] = u, r += String.fromCharCode(n.charCodeAt(d) ^ e[(e[a] + e[t]) % 256]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function _0x33baa6(l, n, u) {
|
||||
return String.fromCharCode(l) + String.fromCharCode(n) + u;
|
||||
}
|
||||
|
||||
function getXB(l) {
|
||||
// douyin
|
||||
let n = _0x39ced2(MD5("d4+pTKoNjJFb5tMtAC3XB9XrDDxlig1kjbh32u+x5YcwWb/me2pvLTh6ZdBVN5skEeIaOYNixbnFK6wyJdl/Lcy9CDAcpXLLQc3QFKIDQ3KkQYie3n258eLS1YFUqFLDjn7dqCRp1jjoORamU2SV"));
|
||||
// douyin & tiktok
|
||||
let u = _0x39ced2(MD5(_0x39ced2("d41d8cd98f00b204e9800998ecf8427e")));
|
||||
let e = _0x1da120(l), t = new Date().getTime() / 1e3, r = 536919696, o = [], d = [], a = "";
|
||||
let c = [ 64, .00390625, 1, 8, e[14], e[15], u[14], u[15], n[14], n[15], t >> 24 & 255, t >> 16 & 255, t >> 8 & 255, t >> 0 & 255, r >> 24 & 255, r >> 16 & 255, r >> 8 & 255, r >> 0 & 255 ];
|
||||
c.push(c.reduce(function(l, n) {
|
||||
return l ^ n;
|
||||
}));
|
||||
for (let l = 0; l < c.length; l += 2) {
|
||||
o.push(c[l]);
|
||||
d.push(c[l + 1]);
|
||||
}
|
||||
//unescape('%FF')
|
||||
let i = _0x33baa6.apply(null, [ 2, 255, _0x330d112.apply(null, [String.fromCharCode(255), _0x330d11.apply(null, o.concat(d).slice(0, 19)) ]) ]);
|
||||
for (let l = 0; l < i.length; ) {
|
||||
a += _0x478bb3(i.charCodeAt(l++), i.charCodeAt(l++), i.charCodeAt(l++));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
_0x180b4c = _0x37f15d();
|
||||
|
||||
module.exports = {
|
||||
getXB: getXB
|
||||
};
|
||||
27
Server/x-tt-params.js
Normal file
27
Server/x-tt-params.js
Normal file
@@ -0,0 +1,27 @@
|
||||
let CryptoJS = require("crypto-js");
|
||||
getXTTP = e => {
|
||||
const t = [];
|
||||
return Object.keys(e).forEach((i => {
|
||||
const o = `${i}=${e[i]}`;
|
||||
t.push(o)
|
||||
})),
|
||||
t.push("is_encryption=1"),
|
||||
((e, t) => {
|
||||
const i = ((e, t) => {
|
||||
let i = e.toString();
|
||||
const o = i.length;
|
||||
return o < 16 ? i = new Array(16 - o + 1).join("0") + i : o > 16 && (i = i.slice(0, 16)),
|
||||
i
|
||||
})("webapp1.0+20210628"),
|
||||
n = CryptoJS.enc.Utf8.parse(i);
|
||||
return CryptoJS.AES.encrypt(e, n, {
|
||||
iv: n,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
}).toString()
|
||||
})(t.join("&"))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getXTTP: getXTTP
|
||||
};
|
||||
Reference in New Issue
Block a user