Skip to content

Commit c20650d

Browse files
committed
optimize tiktok batch download - WIP
1 parent 2490786 commit c20650d

File tree

3 files changed

+193
-13
lines changed

3 files changed

+193
-13
lines changed

scripts/content-scripts/ufs_global.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ export const UfsGlobal = {
9090
zipAndDownloadBlobs,
9191
getBlobFromUrl,
9292
getBlobFromUrlWithProgress,
93+
sanitizeFileName,
94+
getOriginalWindowFunction,
95+
chooseFolderToDownload,
96+
downloadToFolder,
9397
downloadBlobUrl,
9498
downloadBlob,
9599
downloadURL,
@@ -2014,6 +2018,83 @@ async function getBlobFromUrlWithProgress(url, progressCallback) {
20142018

20152019
return blob;
20162020
}
2021+
function sanitizeFileName(fileName) {
2022+
// Định nghĩa các ký tự hợp lệ (chữ cái, số, dấu gạch dưới, dấu gạch ngang và dấu chấm)
2023+
const validChars = /^[a-zA-Z0-9_\-.]+$/;
2024+
2025+
// Lược bỏ các ký tự không hợp lệ
2026+
let sanitizedFileName = "";
2027+
for (let char of fileName) {
2028+
if (validChars.test(char)) {
2029+
sanitizedFileName += char;
2030+
}
2031+
}
2032+
2033+
// Trả về tên tệp đã lược bỏ các ký tự không hợp lệ
2034+
return sanitizedFileName;
2035+
}
2036+
2037+
// Ví dụ sử dụng hàm
2038+
let originalFileName = "tên-tệp!không@hợp#lệ$.txt";
2039+
let sanitizedFileName = sanitizeFileName(originalFileName);
2040+
console.log(sanitizedFileName); // Output: tên-tệpkhônghợpệ.txt
2041+
2042+
// https://stackoverflow.com/a/69543476/11898496
2043+
function getOriginalWindowFunction(fnName) {
2044+
const key = "ufs_original_windown_fn";
2045+
if (!window[key]) window[key] = {};
2046+
2047+
if (!window[key][fnName]) {
2048+
const iframe = document.createElement("iframe");
2049+
2050+
iframe.style.display = "none";
2051+
document.body.appendChild(iframe); // add element
2052+
2053+
window[key][fnName] = iframe.contentWindow[fnName];
2054+
}
2055+
2056+
return window[key][fnName];
2057+
}
2058+
async function chooseFolderToDownload(subDirName = "") {
2059+
const dirHandler = await window.showDirectoryPicker({
2060+
mode: "readwrite",
2061+
// startIn: 'downloads',
2062+
});
2063+
await dirHandler.requestPermission({ writable: true });
2064+
if (!subDirName) return dirHandler;
2065+
2066+
const subDir = await dirHandler.getDirectoryHandle(subDirName, {
2067+
create: true,
2068+
});
2069+
return subDir;
2070+
}
2071+
async function downloadToFolder(url, fileName, dirHandler, subFolderName = "") {
2072+
try {
2073+
const f = getOriginalWindowFunction("fetch");
2074+
2075+
// try download directly, using fetch blob
2076+
const res = await f(url);
2077+
const blob = await res.blob();
2078+
const fileHandler = await dirHandler.getFileHandle(fileName, {
2079+
create: true,
2080+
});
2081+
const writable = await fileHandler.createWritable();
2082+
await writable.write(blob);
2083+
await writable.close();
2084+
return true;
2085+
} catch (e) {
2086+
console.error(e);
2087+
debugger;
2088+
2089+
// backup download: using extension api
2090+
await download({
2091+
url: url,
2092+
conflictAction: "overwrite",
2093+
filename: (subFolderName ? subFolderName + "/" : "") + fileName,
2094+
});
2095+
return false;
2096+
}
2097+
}
20172098

20182099
// TODO use saveAs instead all of these download functions
20192100
async function downloadBlobUrl(url, title) {

scripts/libs/ajax-hook/index.js

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,47 @@ let readyFetch = false;
2626

2727
function initFetch() {
2828
const originalFetch = window.fetch;
29-
window.fetch = async function (url, options) {
29+
window.fetch = async function (urlOrRequest, options) {
30+
let url = urlOrRequest;
31+
if (urlOrRequest instanceof Request) {
32+
url = urlOrRequest?.url;
33+
options = options || {};
34+
for (const key in urlOrRequest) {
35+
const type = typeof urlOrRequest[key];
36+
if (type === "string" || type === "number" || type === "boolean") {
37+
options[key] = urlOrRequest[key];
38+
}
39+
}
40+
}
41+
3042
let request = { url, options };
3143
for (const { fn } of onBeforeFetchFn) {
32-
const res = await fn?.(request.url, request.options);
44+
const res = await fn?.(request.url, request.options)?.catch(
45+
console.error
46+
);
3347
if (res) request = res;
3448
if (res === null) return null;
3549
}
3650

37-
let response = await originalFetch(...request);
51+
if (urlOrRequest instanceof Request) {
52+
try {
53+
// TODO modify options
54+
// for (const key in request.options) {
55+
// urlOrRequest[key] = request.options[key];
56+
// }
57+
if (urlOrRequest.url !== request.url) {
58+
urlOrRequest = new Request(request.url, urlOrRequest);
59+
}
60+
} catch (e) {
61+
debugger;
62+
}
63+
}
64+
65+
let response = await originalFetch(urlOrRequest);
3866
for (const { fn } of onAfterFetchFn) {
39-
const res = await fn?.(request.url, request.options, response);
67+
const res = await fn?.(request.url, request.options, response)?.catch(
68+
console.error
69+
);
4070
if (res) response = res;
4171
if (res === null) return null;
4272
}

scripts/tiktok_batchDownload.js

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { UfsGlobal } from "./content-scripts/ufs_global.js";
2+
import { hookFetch } from "./libs/ajax-hook/index.js";
23
import {
34
downloadTiktokVideoFromUrl,
45
downloadTiktokVideoFromId,
56
} from "./tiktok_GLOBAL.js";
67

8+
const CACHED = {
9+
list: [],
10+
videoById: new Map(),
11+
};
12+
13+
const commId = "ufs_tiktok_batchDownload_startDownload";
14+
715
export default {
816
icon: "https://www.tiktok.com/favicon.ico",
917
name: {
@@ -32,7 +40,46 @@ export default {
3240
},
3341
},
3442

43+
contentScript: {
44+
onDocumentStart: () => {
45+
window.addEventListener("message", async (event) => {
46+
console.log(event.data);
47+
if (event.data?.type === commId) {
48+
const dir = await UfsGlobal.Utils.chooseFolderToDownload("tiktok");
49+
for (const { url, name } of event.data.data) {
50+
await UfsGlobal.Utils.downloadToFolder(url, name, dir);
51+
}
52+
}
53+
});
54+
},
55+
},
56+
3557
pageScript: {
58+
onDocumentStart: () => {
59+
// reference to Cached
60+
window.ufs_tiktok_batchDownload = CACHED;
61+
62+
hookFetch({
63+
onAfter: async (url, options, response) => {
64+
if (url.includes("api/post/item_list")) {
65+
// clone to new response
66+
const res = response.clone();
67+
const json = await res.json();
68+
console.log(json);
69+
70+
if (json?.itemList) {
71+
CACHED.list.push(...json.itemList);
72+
json.itemList.forEach((_) => {
73+
CACHED.videoById.set(_.video.id, {
74+
url: _.video.playAddr,
75+
name: _.desc,
76+
});
77+
});
78+
}
79+
}
80+
},
81+
});
82+
},
3683
onDocumentIdle: async () => {
3784
let checkboxes = [];
3885

@@ -193,24 +240,43 @@ export default {
193240
videoUrls.length
194241
}]`;
195242
try {
196-
console.log(`${progress} Đang tìm link cho video ${queue[0]}`);
197-
progressDiv.innerText = `${progress} Đang tìm link video ${queue[0]}...`;
243+
const url = queue[0];
244+
const id = getId(url);
245+
246+
console.log(`${progress} Đang tìm link cho video ${url}`);
247+
progressDiv.innerText = `${progress} Đang tìm link video ${id}...`;
198248
downloadBtn.innerText = `Đang get link ${progress}...`;
199-
let link = await downloadTiktokVideoFromUrl(queue[0], true);
200249

201-
if (!link) {
202-
link = await downloadTiktokVideoFromId(getId(queue[0]));
203-
}
250+
const cached = CACHED.videoById.get(id);
251+
const link =
252+
cached?.url ||
253+
(await downloadTiktokVideoFromUrl(url, true)) ||
254+
(await downloadTiktokVideoFromId(id));
204255

205256
if (link) {
206257
resultTxt.hidden = false;
207258
resultTxt.value += link + "\n";
208259
let count = resultTxt.value.split("\n").filter((i) => i).length;
209260
resultLabel.innerText = `Link tại đây, ${count} video, copy bỏ vào IDM tải hàng loạt nhé:`;
210261

211-
links.push(link);
262+
// await UfsGlobal.Utils.downloadToFolder(link, id + ".mp4", dir);
263+
// await UfsGlobal.Extension.download({
264+
// url: link,
265+
// conflictAction: "overwrite",
266+
// filename:
267+
// "tiktok/" +
268+
// UfsGlobal.Utils.sanitizeFileName(
269+
// CACHED.videoById.get(id)?.name || id
270+
// ) +
271+
// ".mp4",
272+
// });
273+
links.push({
274+
url: link,
275+
name:
276+
UfsGlobal.Utils.sanitizeFileName(cached?.name || id) + ".mp4",
277+
});
212278
} else {
213-
progressDiv.innerText = `[LỖI] Không thể tải video ${queue[0]}.`;
279+
progressDiv.innerText = `[LỖI] Không thể tải video ${url}.`;
214280
await sleep(1000);
215281
}
216282
queue.shift();
@@ -226,7 +292,10 @@ export default {
226292
downloadBtn.disabled = false;
227293
downloadBtn.innerText = "GET LINK 🔗";
228294

229-
if (links?.length) UfsGlobal.Utils.copyToClipboard(links.join("\n"));
295+
if (links?.length) {
296+
UfsGlobal.Utils.copyToClipboard(links.map((_) => _.url).join("\n"));
297+
// window.postMessage({ type: commId, data: links }, "*"); // send to content script to download
298+
}
230299
console.log(links);
231300
}
232301

0 commit comments

Comments
 (0)