diff --git a/.github/workflows/BrowserExtension.yml b/.github/workflows/BrowserExtension.yml
new file mode 100644
index 0000000..d51e288
--- /dev/null
+++ b/.github/workflows/BrowserExtension.yml
@@ -0,0 +1,38 @@
+# This is a basic workflow to help you get started with Actions
+
+name: Generate browser extension
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the "main" branch
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "build"
+ build:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v4
+
+ # Runs a single command using the runners shell
+ - name: Install dependencies & build extension
+ run: cd extension && npm i && node build.js
+ - name: Move files
+ run: cd extension && mv output.zip extension.zip
+ - name: Get last commit SHA
+ run: echo "SHORT_COMMIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
+ - name: Create Release
+ run: gh release create ${{ env.SHORT_COMMIT_SHA }} -t ${{ env.SHORT_COMMIT_SHA }} -n "Note that this release is automatically generated. No testing has been done." --generate-notes "extension/extension.zip"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e843540
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+input.js
+TempLinks
+node_modules
+extension/output
+extension/output.zip
+.noupload
+node
\ No newline at end of file
diff --git a/README.md b/README.md
index f00ad4e..556728d 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,15 @@ etc. from TikTok, and creates a script to download them with yt-dlp
## Instructions
+### From the extension
+
+Download the extension by following the instructions you can find in
+[the extension README](./extension/README.md). Then, change the conversion
+options (if you want to), and click on the button to start the conversion. The
+extesion will take care of everything else.
+
+### From the console
+
Open the TikTok webpage of the user/sound/etc. you want all the videos
downloaded. Press Ctrl (or Cmd if you are on a Mac) + Shift + I to open the
Developer Tools. Go into the Console tab on the top (if you don't see it, click
@@ -14,6 +23,8 @@ file. If you prefer a minified version, you can find that
[here](https://raw.githubusercontent.com/Dinoosauro/tiktok-to-ytdlp/main/script.min.js).
Press enter.
+### Next steps
+
The webpage will automatically scroll until no other items are found. Then, a
file called "TikTokLinks.txt" will be downloaded. You now can download the
videos with yt-dlp. An example script is:
diff --git a/extension/README.md b/extension/README.md
new file mode 100644
index 0000000..8c0053b
--- /dev/null
+++ b/extension/README.md
@@ -0,0 +1,70 @@
+# Browser extension
+
+Hello there! Here you can find the source code of the tiktok-to-ytdlp extension.
+By installing it, you can use this script from a graphical UI, without needing
+to paste anything in the console.
+
+This makes also possible using tiktok-to-ytdlp on mobile browsers, especially on
+Firefox for Android. If you're on iOS, look for an extension similar to
+TamperMonkey for Safari.
+
+## Installation guide
+
+While you can build the extension yourself, I recommend you to download the
+built zip file
+[from GitHub Releases](https://github.com/Dinoosauro/tiktok-to-ytdlp/releases/latest).
+If you still want to build it, look at the instructions below.
+
+### Chromium
+
+If you still have a Chromium-based browser
+
+1. ~~Switch to Firefox~~ Unzip the extension file
+2. Go to the `chrome://extensions` page, and enable the `Developer mode` slider
+3. Click on `Load unpacked extension`, and then choose the folder where you've
+ unzipped the file.
+4. You can now use tiktok-to-ytdlp. You should find it in the extension tray (or
+ on the dropdown menu that appears after clicking the extension icon)
+
+### Firefox
+
+Note: In the future, I might release this on Mozilla Addons. For now, follow
+these steps for sideloading it:
+
+1. Go to `about:debugging#/runtime/this-firefox`
+2. Click on `Load Temporary Add-on`
+3. Choose the .zip file
+4. The extension is installed!
+
+## Building guide
+
+You'll need a recent version of Node.js for building it. First, clone **the
+entire repository**. Open it in the terminal, and go to the `extension`
+directory (`cd extension`). Now install the dependencies for the build script
+(`npm i`), and finally run the script (`node build.js`).
+
+You'll find two new things:
+
+- A folder, `output`, with everything needed by the extension
+- A zip file, `output.zip`, that you can use for sideloading it on Firefox.
+
+## The UI
+
+At the top, you can switch between three sections, so that you can edit all the
+settings of tiktok-to-ytdlp.
+
+![The main UI](./readme_images/MainUI.jpg)
+
+Then, at the bottom, you can start the conversion.
+
+![The scroll UI, with the button to start the operation visible](./readme_images/ScrollUI.jpg)
+
+While the script fetches the videos, you can also obtain a partial .txt file of
+the link obtained up to that time.
+
+![The UI while the conversion is being done](./readme_images/ConvertingUI.jpg)
+
+## Issues
+
+If you have any issues with the building process, or in general with the
+extension or the script, feel free to open an issue here on GitHub.
diff --git a/extension/build.js b/extension/build.js
new file mode 100644
index 0000000..f0ac510
--- /dev/null
+++ b/extension/build.js
@@ -0,0 +1,14 @@
+const fs = require("fs");
+const zip = require("jszip");
+(fs.existsSync("output")) && fs.rmSync("output", { recursive: true });
+fs.mkdirSync("output");
+require("child_process").execSync("cd code/vite && npm i && npx vite build --base=ui");
+require("fs-extra").moveSync(`./code/vite/dist`, `./output/ui`);
+fs.mkdirSync("output/icons");
+for (let item of ["manifest.json", "icons/16.png", "icons/48.png", "icons/128.png"]) fs.copyFileSync(`./code/${item}`, `./output/${item}`);
+let firstScript = fs.readFileSync(`../script.js`, "utf-8");
+firstScript = firstScript.substring(0, firstScript.indexOf("nodeElaborateCustomArgs();"));
+fs.writeFileSync(`./output/extensionHandler.js`, `${firstScript}${fs.readFileSync("./code/extensionHandler.js", "utf-8")}`);
+const zipFile = new zip();
+for (let file of fs.readdirSync("./output", { recursive: true })) if (fs.statSync(`./output/${file}`).isFile()) zipFile.file(file, fs.readFileSync(`./output/${file}`), { createFolders: true });
+zipFile.generateAsync({ type: "nodebuffer" }).then((buffer) => fs.writeFileSync("output.zip", buffer));
diff --git a/extension/code/.vscode/extensions.json b/extension/code/.vscode/extensions.json
new file mode 100644
index 0000000..bdef820
--- /dev/null
+++ b/extension/code/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["svelte.svelte-vscode"]
+}
diff --git a/extension/code/extensionHandler.js b/extension/code/extensionHandler.js
new file mode 100644
index 0000000..a57b2ac
--- /dev/null
+++ b/extension/code/extensionHandler.js
@@ -0,0 +1,35 @@
+/**
+ * Send to the UI the current "conversion" status
+ */
+function updateClient() {
+ (chrome ?? browser).runtime.sendMessage((chrome ?? browser).runtime.id, { operation: scriptOptions.node.resolve && !scriptOptions.node.isResolveTime });
+}
+(chrome ?? browser).runtime.onMessage.addListener((message) => {
+ switch (message.action) {
+ case "start": { // Start running the script
+ scriptOptions = { ...scriptOptions, ...message.content }; // Add the values from the object provided
+ const avoidNullishValues = new Map([ // If those values are null (Svelte might save them as null if nothing is provided), sign them as default
+ ["scrolling_min_time", 1300],
+ ["scrolling_max_time", 2100],
+ ["min_views", -1]
+ ]);
+ for (let [key, value] of avoidNullishValues) if (scriptOptions[key] === null) scriptOptions[key] = value;
+ if (scriptOptions.advanced.maximum_downloads === null) scriptOptions.advanced.maximum_downloads = Infinity;
+ scriptOptions.node.isNode = true; // Mark this script as it is being used in Node via Puppeteer, so that a Promise will be made, resolved when it's time to download everything. While the download would start in any case, in this way we can also notify the extension UI that the conversion has ended.
+ startDownload().then((arr) => {
+ downloadScript(arr.join("\n"), true);
+ updateClient();
+ });
+ updateClient();
+ break;
+ }
+ case "partial": { // Download a part of the script
+ downloadScript(requestTxtNow().join("\n"), true);
+ break;
+ }
+ case "requestOperation": { // Ask if a conversion is being done or not.
+ updateClient();
+ break;
+ }
+ }
+});
diff --git a/extension/code/icons/128.png b/extension/code/icons/128.png
new file mode 100644
index 0000000..d9a45be
Binary files /dev/null and b/extension/code/icons/128.png differ
diff --git a/extension/code/icons/16.png b/extension/code/icons/16.png
new file mode 100644
index 0000000..923b4ff
Binary files /dev/null and b/extension/code/icons/16.png differ
diff --git a/extension/code/icons/48.png b/extension/code/icons/48.png
new file mode 100644
index 0000000..45ebc9c
Binary files /dev/null and b/extension/code/icons/48.png differ
diff --git a/extension/code/manifest.json b/extension/code/manifest.json
new file mode 100644
index 0000000..164ba0a
--- /dev/null
+++ b/extension/code/manifest.json
@@ -0,0 +1,30 @@
+{
+ "manifest_version": 3,
+ "name": "tiktok-to-ytdlp",
+ "description": "Get a .txt file of all the TikTok videos from an user/audio/hashtag/liked videos/saved videos etc, so that they can be downloaded with yt-dlp.",
+ "version": "1.0",
+ "permissions": ["storage"],
+ "action": {
+ "default_popup": "./ui/index.html"
+ },
+ "content_scripts": [
+ {
+ "js": [
+ "extensionHandler.js"
+ ],
+ "matches": [
+ "*://*.tiktok.com/*"
+ ]
+ }
+ ],
+ "icons": {
+ "16": "./icons/16.png",
+ "48": "./icons/48.png",
+ "128": "./icons/128.png"
+ },
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "{07b46526-6164-427c-a135-542654e1da00}"
+ }
+ }
+}
diff --git a/extension/code/vite/.gitignore b/extension/code/vite/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/extension/code/vite/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/extension/code/vite/index.html b/extension/code/vite/index.html
new file mode 100644
index 0000000..de63cf4
--- /dev/null
+++ b/extension/code/vite/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Icons from Microsoft's Fluent UI System Icons This project is not affiliated or endorsed in any way with TikTok,
+ which is a trademark and a product of ByteDance.
+ Note that tiktok-to-ytdlp will stop scrolling when more than x
+ elements are found. If there are more elements in that page, more
+ than x elements will be added in the script. Put a
+ negative number to download every video
+
diff --git a/extension/code/vite/src/lib/Settings/SettingsContainer.ts b/extension/code/vite/src/lib/Settings/SettingsContainer.ts
new file mode 100644
index 0000000..0308ce9
--- /dev/null
+++ b/extension/code/vite/src/lib/Settings/SettingsContainer.ts
@@ -0,0 +1,22 @@
+import { writable } from "svelte/store"
+
+let Settings = writable({
+ scrolling_min_time: 1300, // Change the mininum time the script will try to refresh the page
+ scrolling_max_time: 2100, // Change the maxinum time the script will try to refresh the page
+ min_views: -1, // If a video has fewer views than this, it won't be included in the script.
+ delete_from_next_txt: true, // Delete all the items put in the previous .txt file when asking for a new one. Useful only if you want to obtain a .txt file while scrolling.
+ output_name_type: 2, // Put a string to specify a specific name of the file. Put 0 for trying to fetching it using data tags, 1 for fetching it from the window title, 2 for fetching it from the first "h1" element. _Invalid_ inputs will use the standard "TikTokLinks.txt". This will be edited if a different value is passed from the startDownload() function.
+ adapt_text_output: true, // Replace characters that are prohibited on Windows
+ allow_images: true, // Save also TikTok Image URLs
+ advanced: {
+ get_array_after_scroll: false, // Gets the item links after the webpage is fully scrolled, and not after every scroll.
+ get_link_by_filter: true, // Get the website link by inspecting all the links in the container div, instead of looking for data references.
+ check_nullish_link: true, // Check if a link is nullish and, if true, try with the next video.
+ log_link_error: true, // Write in the console if there's an error when fetching the link.
+ maximum_downloads: Infinity // Change this to a finite number to fetch only a specific number of values. Note that a) more elements might be added to the final file if available; and b) "get_array_after_scroll" must be set to false.
+ },
+ __extension: {
+ fileName: ""
+ }
+});
+export default Settings;
\ No newline at end of file
diff --git a/extension/code/vite/src/main.ts b/extension/code/vite/src/main.ts
new file mode 100644
index 0000000..4d67e2a
--- /dev/null
+++ b/extension/code/vite/src/main.ts
@@ -0,0 +1,8 @@
+import './app.css'
+import App from './App.svelte'
+
+const app = new App({
+ target: document.getElementById('app')!,
+})
+
+export default app
diff --git a/extension/code/vite/src/vite-env.d.ts b/extension/code/vite/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/extension/code/vite/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/extension/code/vite/svelte.config.js b/extension/code/vite/svelte.config.js
new file mode 100644
index 0000000..b0683fd
--- /dev/null
+++ b/extension/code/vite/svelte.config.js
@@ -0,0 +1,7 @@
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+}
diff --git a/extension/code/vite/tsconfig.json b/extension/code/vite/tsconfig.json
new file mode 100644
index 0000000..df56300
--- /dev/null
+++ b/extension/code/vite/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable checkJs if you'd like to use dynamic types in JS.
+ * Note that setting allowJs false does not prevent the use
+ * of JS in `.svelte` files.
+ */
+ "allowJs": true,
+ "checkJs": true,
+ "isolatedModules": true,
+ "moduleDetection": "force"
+ },
+ "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/extension/code/vite/tsconfig.node.json b/extension/code/vite/tsconfig.node.json
new file mode 100644
index 0000000..6c2d870
--- /dev/null
+++ b/extension/code/vite/tsconfig.node.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "noEmit": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/extension/code/vite/vite.config.ts b/extension/code/vite/vite.config.ts
new file mode 100644
index 0000000..d701969
--- /dev/null
+++ b/extension/code/vite/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import { svelte } from '@sveltejs/vite-plugin-svelte'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [svelte()],
+})
diff --git a/extension/package.json b/extension/package.json
new file mode 100644
index 0000000..7a42918
--- /dev/null
+++ b/extension/package.json
@@ -0,0 +1,6 @@
+{
+ "dependencies": {
+ "fs-extra": "^11.2.0",
+ "jszip": "^3.10.1"
+ }
+}
diff --git a/extension/readme_images/ConvertingUI.jpg b/extension/readme_images/ConvertingUI.jpg
new file mode 100644
index 0000000..4a9c215
Binary files /dev/null and b/extension/readme_images/ConvertingUI.jpg differ
diff --git a/extension/readme_images/MainUI.jpg b/extension/readme_images/MainUI.jpg
new file mode 100644
index 0000000..89ee43e
Binary files /dev/null and b/extension/readme_images/MainUI.jpg differ
diff --git a/extension/readme_images/ScrollUI.jpg b/extension/readme_images/ScrollUI.jpg
new file mode 100644
index 0000000..8c17832
Binary files /dev/null and b/extension/readme_images/ScrollUI.jpg differ
diff --git a/script.js b/script.js
index ab31ec1..888ccf5 100644
--- a/script.js
+++ b/script.js
@@ -23,7 +23,7 @@ var scriptOptions = {
function nodeElaborateCustomArgs(customTypes) { // A function that is able to read a double array, composed with [["the property name", "the property value"]], and change the value of the scriptOptions array
if ((customTypes ?? "") !== "") { // If the provided value isn't nullish
customTypes.forEach(e => { // Get each value
- let optionChange = e[0].split("=>"); // The arrow (=>) is used to indicate that the property is in a nested object (ex: advanced=>log_link_error).
+ var optionChange = e[0].split("=>"); // The arrow (=>) is used to indicate that the property is in a nested object (ex: advanced=>log_link_error).
optionChange.length === 1 ? scriptOptions[e[0]] = e[1] : scriptOptions[optionChange[0]][optionChange[1]] = e[1]; // If the length is 1, just change the option. Otherwise, look for the nested object and change its value
});
}
@@ -78,7 +78,8 @@ function addArray() {
}
if (containerSets[0].indexOf(getLink) === -1 && skipLinks.indexOf(getLink) === -1) { // If the link hasn't been used, add it to the ContainerSets.
containerSets[0].push(getLink);
- containerSets[1].push(((getClass[i].querySelector("[data-e2e=video-views]"))?.innerHTML ?? "0").replace("K", "00").replace("M", "00000"));
+ var views = getClass[i].querySelector("[data-e2e=video-views]")?.innerHTML ?? "0";
+ containerSets[1].push(`${views.replace(".", "").replace("K", "00").replace("M", "00000")}${(views.indexOf("K") !== -1 || views.indexOf("M") !== -1) && views.indexOf(".") === -1 ? "0" : ""}`);
}
}
}
@@ -95,9 +96,11 @@ function ytDlpScript() {
}
if (scriptOptions.node.isNode && !scriptOptions.node.isResolveTime) return ytDlpScript.split("\n"); else downloadScript(ytDlpScript); // If the user has requested from Node to get the array, get it
}
-function downloadScript(script) { // Download the script text to a file
- if (scriptOptions.node.isNode) {
+function downloadScript(script, force) { // Download the script text to a file
+ if (scriptOptions.node.isNode && !force) {
if (scriptOptions.node.isResolveTime) scriptOptions.node.resolve(script.split("\n")); else return script.split("\n");
+ scriptOptions.node.resolve = null;
+ scriptOptions.node.isResolveTime = false;
return;
}
var blob = new Blob([script], { type: "text/plain" }); // Create a blob with the text
@@ -123,7 +126,7 @@ function downloadScript(script) { // Download the script text to a file
}
function requestTxtNow() {
// Write requestTxtNow() in the console to obtain the .txt file while converting. Useful if you have lots of items, and you want to start downloading them.
- let value = ytDlpScript();
+ var value = ytDlpScript();
if (scriptOptions.delete_from_next_txt) { // If delete_from_next_txt is enabled, delete the old items, so that only the newer ones will be downloaded.
skipLinks.push(...containerSets[0]);
containerSets = [[], []];
diff --git a/script.min.js b/script.min.js
index 2465217..8d28d22 100644
--- a/script.min.js
+++ b/script.min.js
@@ -1,2 +1,2 @@
-var scriptOptions={scrolling_min_time:1300,scrolling_max_time:2100,min_views:-1,delete_from_next_txt:!0,output_name_type:2,adapt_text_output:!0,allow_images:!0,advanced:{get_array_after_scroll:!1,get_link_by_filter:!0,check_nullish_link:!0,log_link_error:!0,maximum_downloads:1/0},node:{resolve:null,isNode:!1,isResolveTime:!1}};function nodeElaborateCustomArgs(e){""!==(e??"")&&e.forEach(e=>{var t=e[0].split("=>");1===t.length?scriptOptions[e[0]]=e[1]:scriptOptions[t[0]][t[1]]=e[1]})}var height=document.body.scrollHeight,containerSets=[[],[]],skipLinks=[];function loadWebpage(){0===document.querySelectorAll(".tiktok-qmnyxf-SvgContainer").length?(window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}),setTimeout(()=>{height!==document.body.scrollHeight?!scriptOptions.advanced.get_array_after_scroll&&(addArray(),scriptOptions.advanced.maximum_downloads{height=document.body.scrollHeight,loadWebpage()},Math.floor(Math.random()*scriptOptions.scrolling_max_time+scriptOptions.scrolling_min_time)):setTimeout(()=>{(0===document.querySelectorAll(".tiktok-qmnyxf-SvgContainer").length&&height==document.body.scrollHeight?(scriptOptions.node.isResolveTime=!0,ytDlpScript):loadWebpage)()},3500)},150)):setTimeout(function(){loadWebpage()},1e3)}function addArray(){for(var e,t=document.querySelectorAll(".tiktok-x6y88p-DivItemContainerV2, .css-x6y88p-DivItemContainerV2, .css-1soki6-DivItemContainerForSearch"),i=0;i-1!==e.href.indexOf("/video/")||-1!==e.href.indexOf("/photo/"))[0]:t[i].querySelector("[data-e2e=user-post-item-desc], [data-e2e=user-liked-item], [data-e2e=music-item], [data-e2e=user-post-item], [data-e2e=favorites-item], [data-e2e=challenge-item], [data-e2e=search_top-item]")?.querySelector("a"))?.href,!scriptOptions.allow_images&&-1!==e.indexOf("/photo/")||(scriptOptions.advanced.check_nullish_link&&""===(e??"")?scriptOptions.advanced.log_link_error&&console.log("SCRIPT ERROR: Failed to get link!"):-1===containerSets[0].indexOf(e)&&-1===skipLinks.indexOf(e)&&(containerSets[0].push(e),containerSets[1].push((t[i].querySelector("[data-e2e=video-views]")?.innerHTML??"0").replace("K","00").replace("M","00000")))))}function sanitizeName(e){return e.replaceAll("<","‹").replaceAll(">","›").replaceAll(":","∶").replaceAll('"',"″").replaceAll("/","∕").replaceAll("\\","∖").replaceAll("|","¦").replaceAll("?","¿").replaceAll("*","")}function ytDlpScript(){addArray();for(var e="",t=0;t{scriptOptions.node.resolve=e,loadWebpage()});loadWebpage()}nodeElaborateCustomArgs(),startDownload();
\ No newline at end of file
+var scriptOptions={scrolling_min_time:1300,scrolling_max_time:2100,min_views:-1,delete_from_next_txt:!0,output_name_type:2,adapt_text_output:!0,allow_images:!0,advanced:{get_array_after_scroll:!1,get_link_by_filter:!0,check_nullish_link:!0,log_link_error:!0,maximum_downloads:1/0},node:{resolve:null,isNode:!1,isResolveTime:!1}};function nodeElaborateCustomArgs(e){""!==(e??"")&&e.forEach(e=>{var t=e[0].split("=>");1===t.length?scriptOptions[e[0]]=e[1]:scriptOptions[t[0]][t[1]]=e[1]})}var height=document.body.scrollHeight,containerSets=[[],[]],skipLinks=[];function loadWebpage(){0===document.querySelectorAll(".tiktok-qmnyxf-SvgContainer").length?(window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}),setTimeout(()=>{height!==document.body.scrollHeight?!scriptOptions.advanced.get_array_after_scroll&&(addArray(),scriptOptions.advanced.maximum_downloads{height=document.body.scrollHeight,loadWebpage()},Math.floor(Math.random()*scriptOptions.scrolling_max_time+scriptOptions.scrolling_min_time)):setTimeout(()=>{(0===document.querySelectorAll(".tiktok-qmnyxf-SvgContainer").length&&height==document.body.scrollHeight?(scriptOptions.node.isResolveTime=!0,ytDlpScript):loadWebpage)()},3500)},150)):setTimeout(function(){loadWebpage()},1e3)}function addArray(){for(var e,t=document.querySelectorAll(".tiktok-x6y88p-DivItemContainerV2, .css-x6y88p-DivItemContainerV2, .css-1soki6-DivItemContainerForSearch"),i=0;i-1!==e.href.indexOf("/video/")||-1!==e.href.indexOf("/photo/"))[0]:t[i].querySelector("[data-e2e=user-post-item-desc], [data-e2e=user-liked-item], [data-e2e=music-item], [data-e2e=user-post-item], [data-e2e=favorites-item], [data-e2e=challenge-item], [data-e2e=search_top-item]")?.querySelector("a"))?.href,!scriptOptions.allow_images&&-1!==e.indexOf("/photo/")||(scriptOptions.advanced.check_nullish_link&&""===(e??"")?scriptOptions.advanced.log_link_error&&console.log("SCRIPT ERROR: Failed to get link!"):-1===containerSets[0].indexOf(e)&&-1===skipLinks.indexOf(e)&&(containerSets[0].push(e),e=t[i].querySelector("[data-e2e=video-views]")?.innerHTML??"0",containerSets[1].push(""+e.replace(".","").replace("K","00").replace("M","00000")+(-1===e.indexOf("K")&&-1===e.indexOf("M")||-1!==e.indexOf(".")?"":"0")))))}function sanitizeName(e){return e.replaceAll("<","‹").replaceAll(">","›").replaceAll(":","∶").replaceAll('"',"″").replaceAll("/","∕").replaceAll("\\","∖").replaceAll("|","¦").replaceAll("?","¿").replaceAll("*","")}function ytDlpScript(){addArray();for(var e="",t=0;t{scriptOptions.node.resolve=e,loadWebpage()});loadWebpage()}nodeElaborateCustomArgs(),startDownload();
\ No newline at end of file