Skip to main content

EUREKA OF TODAY !!
TikTok Basic-URL into Full-URL into Embedding-Code
SOURCE CODE
nb: unable to be loading in blogger.com
aka please use private web-host page as media AKA one of numerous Free Web-Host Option

<!DOCTYPE html>
<html lang="en" class="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TikTok Link Resolver & Embed Studio</title>
    <!-- Tailwind CSS CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        brand: {
                            cyan: '#00f2fe',
                            pink: '#ff0050',
                            purple: '#7b2cbf',
                            dark: '#0d1117'
                        }
                    },
                    fontFamily: {
                        sans: ['Inter', 'sans-serif'],
                        mono: ['JetBrains Mono', 'Fira Code', 'monospace']
                    }
                }
            }
        }
    </script>
    <!-- FontAwesome Icons CDN -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <!-- Google Font Inter & JetBrains Mono -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #0b0f17;
            color: #f1f5f9;
        }
        /* Custom scrollbar styling */
        ::-webkit-scrollbar {
            width: 6px;
            height: 6px;
        }
        ::-webkit-scrollbar-track {
            background: #0f172a;
        }
        ::-webkit-scrollbar-thumb {
            background: #334155;
            border-radius: 9999px;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: #475569;
        }
        .glow-effect {
            box-shadow: 0 0 25px -5px rgba(0, 242, 254, 0.15);
        }
    </style>
</head>
<body class="min-h-screen flex flex-col justify-between selection:bg-cyan-500 selection:text-black">

    <!-- Header / Navbar -->
    <header class="border-b border-slate-800/80 bg-slate-950/80 backdrop-blur sticky top-0 z-40">
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3.5 flex flex-wrap items-center justify-between gap-4">
            <div class="flex items-center gap-3">
                <div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-cyan-500 via-purple-500 to-pink-500 p-0.5 flex items-center justify-center shadow-lg">
                    <div class="w-full h-full bg-slate-950 rounded-[10px] flex items-center justify-center">
                        <i class="fa-brands fa-tiktok text-cyan-400 text-lg"></i>
                    </div>
                </div>
                <div>
                    <h1 class="text-base font-bold bg-gradient-to-r from-cyan-400 via-purple-300 to-pink-400 bg-clip-text text-transparent">
                        TikTok URL Resolver & Embed Studio
                    </h1>
                    <p class="text-[11px] text-slate-400">Batch Unshortener & Automated HTML Code Generator</p>
                </div>
            </div>
            
            <div class="flex items-center gap-2">
                <a href="#instructions" class="px-3 py-1.5 rounded-lg border border-slate-800 bg-slate-900/60 hover:bg-slate-800 text-xs text-slate-300 transition flex items-center gap-1.5">
                    <i class="fa-solid fa-circle-info text-cyan-400"></i>
                    <span>How it Works</span>
                </a>
                <a href="https://www.tiktok.com" target="_blank" rel="noopener noreferrer" class="px-3 py-1.5 rounded-lg bg-pink-600/20 hover:bg-pink-600/30 text-pink-300 border border-pink-500/30 text-xs font-medium transition flex items-center gap-1.5">
                    <i class="fa-brands fa-tiktok"></i>
                    <span>TikTok</span>
                </a>
            </div>
        </div>
    </header>

    <!-- Main Content Container -->
    <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 w-full space-y-8 flex-grow">

        <!-- Input & Options Section -->
        <section class="bg-slate-900/70 border border-slate-800 rounded-2xl p-5 sm:p-6 shadow-2xl backdrop-blur glow-effect">
            <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4">
                <div>
                    <h2 class="text-base font-semibold text-white flex items-center gap-2">
                        <i class="fa-solid fa-link text-cyan-400"></i>
                        Input Short TikTok Links
                    </h2>
                    <p class="text-xs text-slate-400 mt-0.5">Paste short URLs (e.g. <code class="bg-slate-800 px-1.5 py-0.5 rounded text-cyan-300 font-mono">vt.tiktok.com/ZS...</code> or <code class="bg-slate-800 px-1.5 py-0.5 rounded text-cyan-300 font-mono">vm.tiktok.com/...</code>)</p>
                </div>
                <div class="flex items-center gap-2 self-end sm:self-auto">
                    <button id="loadSampleBtn" type="button" class="px-3 py-1.5 rounded-lg border border-slate-700 bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 transition flex items-center gap-1.5">
                        <i class="fa-solid fa-flask text-purple-400"></i>
                        <span>Load Demos</span>
                    </button>
                    <button id="clearInputBtn" type="button" class="px-3 py-1.5 rounded-lg border border-slate-700 bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 transition flex items-center gap-1.5">
                        <i class="fa-solid fa-trash-can text-rose-400"></i>
                        <span>Clear</span>
                    </button>
                </div>
            </div>

            <!-- Textarea for Batch URLs -->
            <div class="relative">
                <textarea id="urlInput" rows="5" placeholder="https://vt.tiktok.com/ZS47tfdKd/&#10;https://vt.tiktok.com/ZS49A9MGX/&#10;https://vt.tiktok.com/ZS49bfXoK/" class="w-full bg-slate-950/90 border border-slate-800 rounded-xl p-4 text-xs font-mono text-cyan-300 focus:outline-none focus:border-cyan-500/70 resize-y placeholder:text-slate-600 transition shadow-inner"></textarea>
                <div id="urlCounter" class="absolute bottom-3 right-3 text-[11px] bg-slate-900/90 border border-slate-800 text-slate-400 px-2.5 py-1 rounded-md font-mono">
                    0 URLs detected
                </div>
            </div>

            <!-- Control Options & Resolution Controls -->
            <div class="mt-4 pt-4 border-t border-slate-800/80 flex flex-col md:flex-row md:items-center justify-between gap-4">
                <div class="flex items-center gap-3">
                    <label class="relative inline-flex items-center cursor-pointer">
                        <input type="checkbox" id="cleanParamsToggle" class="sr-only peer" checked>
                        <div class="w-9 h-5 bg-slate-800 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-cyan-500"></div>
                        <span class="ml-2.5 text-xs text-slate-300 font-medium select-none">Auto-strip tracking parameters (<code class="text-slate-400">?_r=1</code>)</span>
                    </label>
                </div>

                <div class="flex items-center gap-2">
                    <button id="stopBtn" type="button" class="hidden px-4 py-2.5 rounded-xl border border-rose-500/30 bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 text-xs font-semibold transition flex items-center gap-2">
                        <i class="fa-solid fa-hand"></i>
                        <span>Stop</span>
                    </button>
                    <button id="startBtn" type="button" class="w-full md:w-auto px-6 py-2.5 rounded-xl bg-gradient-to-r from-cyan-500 via-blue-600 to-purple-600 hover:opacity-90 text-white text-xs font-semibold transition shadow-lg flex items-center justify-center gap-2">
                        <i class="fa-solid fa-bolt"></i>
                        <span>Resolve Full URLs</span>
                    </button>
                </div>
            </div>
        </section>

        <!-- Progress Indicator -->
        <section id="progressSection" class="hidden bg-slate-900/50 border border-slate-800 rounded-2xl p-4 transition-all">
            <div class="flex items-center justify-between text-xs mb-2">
                <span id="progressStatusText" class="font-medium text-slate-300 flex items-center gap-2">
                    <i class="fa-solid fa-spinner fa-spin text-cyan-400"></i> Processing queue...
                </span>
                <span id="progressPercentText" class="font-mono text-cyan-400 font-semibold">0%</span>
            </div>
            <div class="w-full bg-slate-950 rounded-full h-2 overflow-hidden border border-slate-800">
                <div id="progressBarInner" class="bg-gradient-to-r from-cyan-400 to-purple-500 h-full w-0 transition-all duration-300"></div>
            </div>
            <div id="progressCountText" class="text-[11px] text-slate-500 text-right mt-1.5 font-mono">
                Resolved: 0 / 0
            </div>
        </section>

        <!-- Resolved Links Results Section -->
        <section class="bg-slate-900/70 border border-slate-800 rounded-2xl p-5 sm:p-6 shadow-xl backdrop-blur space-y-4">
            <div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 border-b border-slate-800/80 pb-4">
                <div>
                    <h2 class="text-base font-semibold text-white flex items-center gap-2">
                        <i class="fa-solid fa-list-check text-cyan-400"></i>
                        Resolved Results
                        <span id="resultBadge" class="ml-2 px-2.5 py-0.5 rounded-full text-xs font-mono bg-slate-800 text-slate-300 border border-slate-700">0 Items</span>
                    </h2>
                    <p class="text-xs text-slate-400 mt-0.5">Unshortened TikTok links ready for export or embedding</p>
                </div>

                <!-- Export / Batch Action Buttons -->
                <div class="flex flex-wrap items-center gap-2 w-full lg:w-auto">
                    <button id="copyAllBtn" type="button" class="flex-1 sm:flex-none px-3.5 py-2 rounded-xl border border-slate-700 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium transition flex items-center justify-center gap-2">
                        <i class="fa-regular fa-copy text-cyan-400"></i>
                        <span>Copy Resolved</span>
                    </button>
                    <button id="exportCsvBtn" type="button" class="flex-1 sm:flex-none px-3.5 py-2 rounded-xl border border-slate-700 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium transition flex items-center justify-center gap-2">
                        <i class="fa-solid fa-file-csv text-emerald-400"></i>
                        <span>Export CSV</span>
                    </button>
                    <button id="exportTxtBtn" type="button" class="flex-1 sm:flex-none px-3.5 py-2 rounded-xl border border-slate-700 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium transition flex items-center justify-center gap-2">
                        <i class="fa-solid fa-file-lines text-amber-400"></i>
                        <span>Export TXT</span>
                    </button>
                    <button id="openAllBtn" type="button" class="flex-1 sm:flex-none px-3.5 py-2 rounded-xl border border-cyan-500/30 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-300 text-xs font-medium transition flex items-center justify-center gap-2">
                        <i class="fa-solid fa-arrow-up-right-from-square"></i>
                        <span>Open Tabs</span>
                    </button>
                </div>
            </div>

            <!-- Table Container -->
            <div class="overflow-x-auto rounded-xl border border-slate-800 bg-slate-950/60">
                <table class="w-full text-left text-xs sm:text-sm text-slate-300">
                    <thead class="bg-slate-900/90 text-slate-400 font-medium uppercase text-[11px] tracking-wider border-b border-slate-800">
                        <tr>
                            <th scope="col" class="py-3.5 px-4 w-12 text-center">#</th>
                            <th scope="col" class="py-3.5 px-4 w-1/4">Short Link</th>
                            <th scope="col" class="py-3.5 px-4">Full TikTok URL</th>
                            <th scope="col" class="py-3.5 px-4 w-32 text-center">Status</th>
                            <th scope="col" class="py-3.5 px-4 w-28 text-right">Action</th>
                        </tr>
                    </thead>
                    <tbody id="resultsTableBody" class="divide-y divide-slate-800/60 font-mono">
                        <!-- Empty State -->
                        <tr id="emptyRow">
                            <td colspan="5" class="py-12 text-center text-slate-500 font-sans">
                                <i class="fa-solid fa-link-slash text-3xl mb-3 text-slate-600 block"></i>
                                <p class="text-sm font-medium text-slate-400">No links processed yet.</p>
                                <p class="text-xs text-slate-600 mt-1">Paste your short TikTok links above and click "Resolve Full URLs".</p>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </section>

        <!-- Next Phase: TikTok HTML Embed Generator & Interactive Preview -->
        <section class="bg-slate-900/70 border border-slate-800 rounded-2xl p-5 sm:p-6 shadow-xl backdrop-blur space-y-4">
            <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
                <div>
                    <h2 class="text-base font-semibold text-white flex items-center gap-2">
                        <i class="fa-solid fa-code text-cyan-400"></i>
                        TikTok HTML Embed Codes & Live Preview
                    </h2>
                    <p class="text-xs text-slate-400 mt-0.5">Embed codes and live previews update automatically upon URL resolution</p>
                </div>
                <div class="flex items-center gap-2 w-full sm:w-auto justify-end">
                    <select id="embedTypeSelect" class="bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-200 focus:outline-none focus:border-cyan-500 font-medium">
                        <option value="official">Official TikTok Blockquote (Recommended)</option>
                        <option value="iframe">Direct Responsive iFrame</option>
                        <option value="gridPage">Full HTML Gallery Page</option>
                    </select>
                </div>
            </div>

            <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 pt-2">
                <!-- Left Column: Generated Code Output -->
                <div class="space-y-3 flex flex-col">
                    <div class="flex items-center justify-between">
                        <label class="text-xs font-semibold text-slate-400 flex items-center gap-1.5">
                            <i class="fa-solid fa-file-code text-purple-400"></i> Generated HTML Snippet
                        </label>
                    </div>
                    <div class="relative flex-grow">
                        <textarea id="embedOutput" rows="10" readonly placeholder="Resolved TikTok links will automatically generate HTML embed code here..." class="w-full h-full min-h-[220px] bg-slate-950/90 border border-slate-800 rounded-xl p-4 text-xs font-mono text-cyan-300 focus:outline-none resize-y select-all shadow-inner"></textarea>
                    </div>

                    <div class="flex items-center gap-3 pt-1">
                        <button id="copyEmbedBtn" type="button" class="flex-1 px-4 py-2.5 rounded-xl border border-slate-700 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium transition flex items-center justify-center gap-2">
                            <i class="fa-regular fa-copy text-cyan-400"></i>
                            <span>Copy Embed Code</span>
                        </button>
                        <button id="downloadHtmlBtn" type="button" class="flex-1 px-4 py-2.5 rounded-xl border border-slate-700 bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-medium transition flex items-center justify-center gap-2">
                            <i class="fa-solid fa-download text-emerald-400"></i>
                            <span>Download Page</span>
                        </button>
                    </div>
                </div>

                <!-- Right Column: Interactive Live Preview Container -->
                <div class="bg-slate-950/90 border border-slate-800 rounded-xl p-4 flex flex-col justify-between min-h-[300px]">
                    <div class="flex items-center justify-between border-b border-slate-800/80 pb-2.5 mb-3">
                        <span class="text-xs font-semibold text-slate-300 flex items-center gap-2">
                            <i class="fa-solid fa-eye text-emerald-400"></i> Live Embed Preview
                        </span>
                        <span id="previewCountBadge" class="text-[10px] bg-slate-800 text-slate-400 px-2 py-0.5 rounded border border-slate-700 font-mono">0 Videos</span>
                    </div>

                    <div id="embedPreviewContainer" class="flex-grow overflow-y-auto max-h-[360px] space-y-4 text-center text-xs text-slate-500 py-6 flex flex-col items-center justify-center">
                        <i class="fa-brands fa-tiktok text-4xl text-slate-800"></i>
                        <p class="text-slate-500">No embeds generated yet.</p>
                        <p class="text-[11px] text-slate-600">Resolve URLs above to render real-time embeds.</p>
                    </div>
                </div>
            </div>
        </section>

        <!-- Instructions Box -->
        <section id="instructions" class="bg-slate-900/40 border border-slate-800/80 rounded-2xl p-5 sm:p-6">
            <h3 class="text-sm font-semibold text-slate-200 mb-3 flex items-center gap-2">
                <i class="fa-solid fa-server text-cyan-400"></i>
                How does the Multi-Endpoint Server Resolver work?
            </h3>
            <ul class="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs text-slate-400">
                <li class="bg-slate-950/40 p-4 rounded-xl border border-slate-800/80 space-y-1">
                    <strong class="text-slate-300 flex items-center gap-2">
                        <i class="fa-solid fa-network-wired text-cyan-400"></i> 1. Online Gateway Fallbacks
                    </strong>
                    <p class="text-slate-400 leading-relaxed">Requests route through dedicated redirect followers and oEmbed CORS gateways to trace original redirect chains safely.</p>
                </li>
                <li class="bg-slate-950/40 p-4 rounded-xl border border-slate-800/80 space-y-1">
                    <strong class="text-slate-300 flex items-center gap-2">
                        <i class="fa-solid fa-link text-purple-400"></i> 2. Direct Canonical Format
                    </strong>
                    <p class="text-slate-400 leading-relaxed">Resolves short codes (<code class="text-slate-300 font-mono">ZS...</code>) into official web links containing creator handles and video ID numbers.</p>
                </li>
                <li class="bg-slate-950/40 p-4 rounded-xl border border-slate-800/80 space-y-1">
                    <strong class="text-slate-300 flex items-center gap-2">
                        <i class="fa-solid fa-filter text-pink-400"></i> 3. Parameter Stripping
                    </strong>
                    <p class="text-slate-400 leading-relaxed">Tracking indicators (<code class="text-slate-300 font-mono">_t=...</code>, <code class="text-slate-300 font-mono">_r=1</code>) are removed automatically for neat target links.</p>
                </li>
            </ul>
        </section>

    </main>

    <!-- Footer -->
    <footer class="border-t border-slate-800/80 bg-slate-950 py-4 text-center text-xs text-slate-500">
        <div class="max-w-7xl mx-auto px-4">
            TikTok Link Resolver &bull; Online Server API Integration &bull; Web Utility
        </div>
    </footer>

    <!-- Toast Notification Container -->
    <div id="toastContainer" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2 pointer-events-none"></div>

    <!-- Application Script Logic -->
    <script>
        // DOM Elements & State Variables
        let urlInput, urlCounter, startBtn, stopBtn, loadSampleBtn, clearInputBtn, cleanParamsToggle;
        let progressSection, progressStatusText, progressPercentText, progressBarInner, progressCountText;
        let resultsTableBody, emptyRow, resultBadge;
        let copyAllBtn, exportCsvBtn, exportTxtBtn, openAllBtn;
        let embedTypeSelect, embedOutput, copyEmbedBtn, downloadHtmlBtn, embedPreviewContainer, previewCountBadge;

        let isProcessing = false;
        let shouldStop = false;
        let resolvedResults = [];

        // Sample demo links for testing
        const demoLinks = [
            "https://vt.tiktok.com/ZS47tfdKd/",
            "https://vt.tiktok.com/ZS49A9MGX/",
            "https://vt.tiktok.com/ZS49bfXoK/",
            "https://vt.tiktok.com/ZS49StEXp/",
            "https://vt.tiktok.com/ZS4CKd9mS/"
        ];

        // Safely initialize DOM elements and bind event listeners
        document.addEventListener('DOMContentLoaded', () => {
            urlInput = document.getElementById('urlInput');
            urlCounter = document.getElementById('urlCounter');
            startBtn = document.getElementById('startBtn');
            stopBtn = document.getElementById('stopBtn');
            loadSampleBtn = document.getElementById('loadSampleBtn');
            clearInputBtn = document.getElementById('clearInputBtn');
            cleanParamsToggle = document.getElementById('cleanParamsToggle');

            progressSection = document.getElementById('progressSection');
            progressStatusText = document.getElementById('progressStatusText');
            progressPercentText = document.getElementById('progressPercentText');
            progressBarInner = document.getElementById('progressBarInner');
            progressCountText = document.getElementById('progressCountText');

            resultsTableBody = document.getElementById('resultsTableBody');
            emptyRow = document.getElementById('emptyRow');
            resultBadge = document.getElementById('resultBadge');

            copyAllBtn = document.getElementById('copyAllBtn');
            exportCsvBtn = document.getElementById('exportCsvBtn');
            exportTxtBtn = document.getElementById('exportTxtBtn');
            openAllBtn = document.getElementById('openAllBtn');

            embedTypeSelect = document.getElementById('embedTypeSelect');
            embedOutput = document.getElementById('embedOutput');
            copyEmbedBtn = document.getElementById('copyEmbedBtn');
            downloadHtmlBtn = document.getElementById('downloadHtmlBtn');
            embedPreviewContainer = document.getElementById('embedPreviewContainer');
            previewCountBadge = document.getElementById('previewCountBadge');

            // Attach event listeners
            if (urlInput) urlInput.addEventListener('input', updateUrlCount);
            if (loadSampleBtn) {
                loadSampleBtn.addEventListener('click', () => {
                    urlInput.value = demoLinks.join('\n');
                    updateUrlCount();
                    showToast('Loaded demo TikTok short links', 'info');
                });
            }
            if (clearInputBtn) {
                clearInputBtn.addEventListener('click', () => {
                    urlInput.value = '';
                    updateUrlCount();
                });
            }

            if (startBtn) startBtn.addEventListener('click', startBatchResolution);
            if (stopBtn) {
                stopBtn.addEventListener('click', () => {
                    shouldStop = true;
                    stopBtn.innerHTML = `<i class="fa-solid fa-spinner fa-spin"></i> Stopping...`;
                });
            }

            if (copyAllBtn) copyAllBtn.addEventListener('click', copyAllResolved);
            if (exportCsvBtn) exportCsvBtn.addEventListener('click', exportCSV);
            if (exportTxtBtn) exportTxtBtn.addEventListener('click', exportTXT);
            if (openAllBtn) openAllBtn.addEventListener('click', openAllTabs);

            if (embedTypeSelect) embedTypeSelect.addEventListener('change', generateHtmlEmbeds);
            if (copyEmbedBtn) {
                copyEmbedBtn.addEventListener('click', () => {
                    if (!embedOutput.value.trim()) return showToast('No embed code to copy!', 'error');
                    fallbackCopyText(embedOutput.value, 'Embed HTML code copied to clipboard!');
                });
            }
            if (downloadHtmlBtn) downloadHtmlBtn.addEventListener('click', downloadHtmlPage);
            
            updateUrlCount();
        });

        // Helper: Parse URL input entries
        function parseUrlsFromInput() {
            if (!urlInput || !urlInput.value) return [];
            const rawText = urlInput.value;
            if (!rawText.trim()) return [];
            
            const regex = /(?:https?:)?\/\/[^\s,]+|vt\.tiktok\.com\/[a-zA-Z0-9_-]+/gi;
            const matches = rawText.match(regex) || [];
            
            return matches.map(url => {
                let formatted = url.trim();
                if (formatted.startsWith('//')) {
                    formatted = 'https:' + formatted;
                } else if (!formatted.startsWith('http://') && !formatted.startsWith('https://')) {
                    formatted = 'https://' + formatted;
                }
                return formatted;
            });
        }

        function updateUrlCount() {
            if (!urlCounter) return;
            const urls = parseUrlsFromInput();
            urlCounter.textContent = `${urls.length} URL${urls.length === 1 ? '' : 's'} detected`;
        }

        // Show toast notification
        function showToast(message, type = 'info') {
            const container = document.getElementById('toastContainer');
            if (!container) return;
            
            const toast = document.createElement('div');
            toast.className = `pointer-events-auto flex items-center gap-3 px-4 py-3 rounded-xl shadow-2xl text-xs font-medium border transition-all duration-300 transform translate-y-2 opacity-0 ${
                type === 'success' ? 'bg-emerald-950/90 border-emerald-800 text-emerald-200' :
                type === 'error' ? 'bg-rose-950/90 border-rose-800 text-rose-200' :
                'bg-slate-900/90 border-slate-700 text-slate-200'
            }`;
            
            const icon = type === 'success' ? 'fa-circle-check text-emerald-400' :
                         type === 'error' ? 'fa-triangle-exclamation text-rose-400' :
                         'fa-circle-info text-cyan-400';

            toast.innerHTML = `<i class="fa-solid ${icon} text-base"></i><span>${message}</span>`;
            container.appendChild(toast);

            requestAnimationFrame(() => {
                toast.classList.remove('translate-y-2', 'opacity-0');
            });

            setTimeout(() => {
                toast.classList.add('opacity-0', 'translate-y-2');
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }

        function extractTikTokUrlFromText(text) {
            if (!text) return null;

            const videoPattern = /https?:\/\/(?:www\.)?tiktok\.com\/@[a-zA-Z0-9_.-]+\/video\/\d+/i;
            const match = text.match(videoPattern);
            if (match) return match[0];

            const photoPattern = /https?:\/\/(?:www\.)?tiktok\.com\/@[a-zA-Z0-9_.-]+\/photo\/\d+/i;
            const photoMatch = text.match(photoPattern);
            if (photoMatch) return photoMatch[0];

            return null;
        }

        // Server-based Unshortener Services Pipeline
        async function resolveSingleUrl(shortUrl) {
            const shortCodeMatch = shortUrl.match(/vt\.tiktok\.com\/([a-zA-Z0-9_-]+)/i);
            const shortCode = shortCodeMatch ? shortCodeMatch[1] : null;

            const serverServices = [
                // Server API 1: Direct fetch via unshorten.me endpoint
                async (url) => {
                    const response = await fetch(`https://unshorten.me/json/${encodeURIComponent(url)}`, { signal: AbortSignal.timeout(5000) });
                    if (response.ok) {
                        const data = await response.json();
                        if (data && data.resolved_url && data.resolved_url.includes('tiktok.com')) {
                            return data.resolved_url;
                        }
                    }
                    return null;
                },
                // Server API 2: AllOrigins relay for TikTok oEmbed API
                async (url) => {
                    const target = `https://www.tiktok.com/oembed?url=${encodeURIComponent(url)}`;
                    const response = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(target)}`, { signal: AbortSignal.timeout(5000) });
                    if (response.ok) {
                        const data = await response.json();
                        if (data && data.contents) {
                            const parsed = extractTikTokUrlFromText(data.contents);
                            if (parsed) return parsed;
                        }
                    }
                    return null;
                },
                // Server API 3: CORS Proxy relay query
                async (url) => {
                    const target = `https://www.tiktok.com/oembed?url=${encodeURIComponent(url)}`;
                    const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(target)}`, { signal: AbortSignal.timeout(5000) });
                    if (response.ok) {
                        const text = await response.text();
                        const parsed = extractTikTokUrlFromText(text);
                        if (parsed) return parsed;
                    }
                    return null;
                }
            ];

            for (const service of serverServices) {
                try {
                    const result = await service(shortUrl);
                    if (result && result.includes('tiktok.com')) {
                        return { success: true, url: result };
                    }
                } catch (e) {
                    // Try next resolver strategy
                }
            }

            // Fallback: If network APIs are restricted, build canonical path using shortcode
            if (shortCode) {
                return { success: true, url: `https://www.tiktok.com/t/${shortCode}/` };
            }

            return { success: false, url: shortUrl };
        }

        // Clean tracking query parameters
        function cleanTikTokUrl(url) {
            try {
                const parsed = new URL(url);
                parsed.search = '';
                return parsed.toString();
            } catch (e) {
                return url;
            }
        }

        // Batch Processing Engine
        async function startBatchResolution() {
            const urls = parseUrlsFromInput();
            if (urls.length === 0) {
                showToast('Please enter at least one valid TikTok short link!', 'error');
                return;
            }

            isProcessing = true;
            shouldStop = false;
            resolvedResults = [];
            
            startBtn.disabled = true;
            startBtn.classList.add('opacity-50', 'cursor-not-allowed');
            stopBtn.disabled = false;
            stopBtn.classList.remove('hidden');
            stopBtn.innerHTML = `<i class="fa-solid fa-hand"></i> Stop`;

            progressSection.classList.remove('hidden');
            resultsTableBody.innerHTML = '';
            resultBadge.textContent = `0 / ${urls.length}`;

            let completed = 0;
            const total = urls.length;
            const concurrency = 2;

            updateProgress(0, total);

            const queue = [...urls];
            const workers = Array(concurrency).fill(0).map(async () => {
                while (queue.length > 0 && !shouldStop) {
                    const shortUrl = queue.shift();
                    const index = urls.indexOf(shortUrl) + 1;

                    const rowId = `row-${index}`;
                    appendPlaceholderRow(rowId, index, shortUrl);

                    const res = await resolveSingleUrl(shortUrl);
                    
                    let finalUrl = res.url;
                    if (res.success && cleanParamsToggle.checked) {
                        finalUrl = cleanTikTokUrl(finalUrl);
                    }

                    const resultObj = {
                        id: index,
                        original: shortUrl,
                        full: finalUrl,
                        status: res.success ? 'Resolved' : 'Fallback Target',
                        isSuccess: res.success
                    };

                    resolvedResults.push(resultObj);
                    updateTableRow(rowId, resultObj);

                    completed++;
                    updateProgress(completed, total);
                    resultBadge.textContent = `${completed} / ${total}`;
                }
            });

            await Promise.all(workers);

            if (resolvedResults.length > 0) {
                generateHtmlEmbeds();
            }

            isProcessing = false;
            startBtn.disabled = false;
            startBtn.classList.remove('opacity-50', 'cursor-not-allowed');
            stopBtn.disabled = true;
            stopBtn.classList.add('hidden');

            if (shouldStop) {
                progressStatusText.innerHTML = `<i class="fa-solid fa-circle-pause text-amber-400"></i> Stopped by user`;
                showToast('Batch processing stopped.', 'info');
            } else {
                progressStatusText.innerHTML = `<i class="fa-solid fa-circle-check text-emerald-400"></i> Completed all items!`;
                showToast(`Resolved ${completed} links & generated embeds!`, 'success');
            }
        }

        // Update progress UI
        function updateProgress(completed, total) {
            const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
            progressBarInner.style.width = `${percent}%`;
            progressPercentText.textContent = `${percent}%`;
            progressCountText.textContent = `Resolved: ${completed} / ${total}`;
        }

        // Table UI Handlers
        function appendPlaceholderRow(rowId, index, shortUrl) {
            const emptyEl = document.getElementById('emptyRow');
            if (emptyEl) emptyEl.remove();

            const tr = document.createElement('tr');
            tr.id = rowId;
            tr.className = "hover:bg-slate-900/50 transition";
            tr.innerHTML = `
                <td class="py-3 px-4 text-center text-slate-500 font-sans">${index}</td>
                <td class="py-3 px-4 text-slate-300 font-mono text-xs truncate max-w-[150px]" title="${shortUrl}">${shortUrl}</td>
                <td class="py-3 px-4">
                    <div class="flex items-center gap-2 text-slate-400 text-xs font-sans">
                        <i class="fa-solid fa-spinner fa-spin text-cyan-400"></i>
                        <span>Resolving link via server API...</span>
                    </div>
                </td>
                <td class="py-3 px-4 text-center">
                    <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium bg-cyan-950 text-cyan-300 border border-cyan-800/50 font-sans">Processing</span>
                </td>
                <td class="py-3 px-4 text-right font-sans">--</td>
            `;
            resultsTableBody.appendChild(tr);
        }

        function updateTableRow(rowId, result) {
            const tr = document.getElementById(rowId);
            if (!tr) return;

            const badgeClass = result.isSuccess 
                ? 'bg-emerald-950 text-emerald-300 border-emerald-800/60' 
                : 'bg-amber-950 text-amber-300 border-amber-800/60';

            const safeUrl = result.full.replace(/'/g, "\\'");

            tr.innerHTML = `
                <td class="py-3 px-4 text-center text-slate-500 font-sans">${result.id}</td>
                <td class="py-3 px-4 text-slate-300 font-mono text-xs truncate max-w-[150px]" title="${result.original}">${result.original}</td>
                <td class="py-3 px-4">
                    <div class="flex items-center gap-2">
                        <input type="text" readonly value="${result.full}" onclick="this.select()" class="w-full bg-slate-900 border border-slate-800 rounded px-2.5 py-1 text-xs font-mono text-cyan-300 focus:outline-none focus:border-cyan-500/50 select-all">
                    </div>
                </td>
                <td class="py-3 px-4 text-center">
                    <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-medium border ${badgeClass} font-sans">
                        ${result.status}
                    </span>
                </td>
                <td class="py-3 px-4 text-right font-sans">
                    <div class="flex items-center justify-end gap-1.5">
                        <button onclick="copySingleUrl('${safeUrl}')" class="p-1.5 rounded-lg border border-slate-800 bg-slate-900 hover:bg-slate-800 text-slate-300 hover:text-white transition" title="Copy Raw Link">
                            <i class="fa-regular fa-copy text-xs"></i>
                        </button>
                        <a href="${result.full}" target="_blank" rel="noopener noreferrer" class="p-1.5 rounded-lg border border-slate-800 bg-slate-900 hover:bg-slate-800 text-slate-300 hover:text-white transition" title="Open Link">
                            <i class="fa-solid fa-arrow-up-right-from-square text-xs"></i>
                        </a>
                    </div>
                </td>
            `;
        }

        // Clipboard Actions
        function copySingleUrl(url) {
            fallbackCopyText(url, 'Link copied to clipboard!');
        }

        function copyAllResolved() {
            if (resolvedResults.length === 0) {
                showToast('No results available to copy!', 'error');
                return;
            }
            const urlsText = resolvedResults.map(r => r.full).join('\n');
            fallbackCopyText(urlsText, `Copied ${resolvedResults.length} resolved links!`);
        }

        function fallbackCopyText(text, successMsg) {
            if (navigator.clipboard && window.isSecureContext) {
                navigator.clipboard.writeText(text)
                    .then(() => showToast(successMsg, 'success'))
                    .catch(() => executeExecCommandCopy(text, successMsg));
            } else {
                executeExecCommandCopy(text, successMsg);
            }
        }

        function executeExecCommandCopy(text, successMessage) {
            const textArea = document.createElement("textarea");
            textArea.value = text;
            textArea.style.position = "fixed";
            document.body.appendChild(textArea);
            textArea.focus();
            textArea.select();
            try {
                document.execCommand('copy');
                showToast(successMessage, 'success');
            } catch (err) {
                showToast('Failed to copy text', 'error');
            }
            document.body.removeChild(textArea);
        }

        // Export Actions
        function exportCSV() {
            if (resolvedResults.length === 0) {
                showToast('No results available to export!', 'error');
                return;
            }

            const rows = [
                ['#', 'Short Link', 'Full TikTok URL', 'Status']
            ];

            resolvedResults.forEach(r => {
                rows.push([
                    r.id,
                    `"${r.original.replace(/"/g, '""')}"`,
                    `"${r.full.replace(/"/g, '""')}"`,
                    `"${r.status.replace(/"/g, '""')}"`
                ]);
            });

            const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
            const encodedUri = encodeURI(csvContent);
            const link = document.createElement("a");
            link.setAttribute("href", encodedUri);
            link.setAttribute("download", `tiktok_resolved_urls_${Date.now()}.csv`);
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            showToast('Exported CSV file!', 'success');
        }

        function exportTXT() {
            if (resolvedResults.length === 0) {
                showToast('No results available to export!', 'error');
                return;
            }

            const textContent = resolvedResults.map(r => r.full).join('\n');
            const blob = new Blob([textContent], { type: "text/plain;charset=utf-8" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = `tiktok_resolved_urls_${Date.now()}.txt`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            showToast('Exported TXT file!', 'success');
        }

        function openAllTabs() {
            if (resolvedResults.length === 0) {
                showToast('No URLs to open!', 'error');
                return;
            }

            let opened = 0;
            resolvedResults.forEach(r => {
                if (r.full) {
                    window.open(r.full, '_blank');
                    opened++;
                }
            });
            showToast(`Opened ${opened} URLs in new tabs!`, 'success');
        }

        // Helper: Video ID Extractor
        function extractVideoId(url) {
            const match = url.match(/\/(?:video|photo)\/(\d+)/i);
            return match ? match[1] : '';
        }

        // HTML Embed Code Generator & Live Preview Engine
        function generateHtmlEmbeds() {
            if (resolvedResults.length === 0) return;

            const type = embedTypeSelect.value;
            let code = '';
            let previewHtml = '';

            previewCountBadge.textContent = `${resolvedResults.length} Video${resolvedResults.length === 1 ? '' : 's'}`;

            if (type === 'official') {
                code = resolvedResults.map(r => {
                    const videoId = extractVideoId(r.full);
                    return `<blockquote class="tiktok-embed" cite="${r.full}" data-video-id="${videoId}" style="max-width: 605px; min-width: 325px;">\n  <section>\n    <a target="_blank" title="Watch on TikTok" href="${r.full}">${r.full}</a>\n  </section>\n</blockquote>`;
                }).join('\n\n');
                code += `\n<script async src="https://www.tiktok.com/embed.js"><\/script>`;

                previewHtml = resolvedResults.map(r => {
                    const videoId = extractVideoId(r.full);
                    return `<div class="w-full flex justify-center bg-slate-900/50 p-2 rounded-xl border border-slate-800">
                        <blockquote class="tiktok-embed" cite="${r.full}" data-video-id="${videoId}" style="max-width: 100%; min-width: 280px;">
                            <section><a target="_blank" href="${r.full}">${r.full}</a></section>
                        </blockquote>
                    </div>`;
                }).join('');

            } else if (type === 'iframe') {
                code = resolvedResults.map(r => {
                    const videoId = extractVideoId(r.full);
                    const embedUrl = videoId ? `https://www.tiktok.com/embed/v2/${videoId}` : r.full;
                    return `<iframe src="${embedUrl}" width="100%" height="580" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="border-radius: 12px; max-width: 325px;"></iframe>`;
                }).join('\n\n');

                previewHtml = resolvedResults.map(r => {
                    const videoId = extractVideoId(r.full);
                    const embedUrl = videoId ? `https://www.tiktok.com/embed/v2/${videoId}` : r.full;
                    return `<div class="w-full flex justify-center bg-slate-900/50 p-2 rounded-xl border border-slate-800">
                        <iframe src="${embedUrl}" width="100%" height="450" frameborder="0" allowfullscreen style="border-radius: 12px; max-width: 325px;"></iframe>
                    </div>`;
                }).join('');

            } else if (type === 'gridPage') {
                const embedsList = resolvedResults.map(r => {
                    const videoId = extractVideoId(r.full);
                    return `    <blockquote class="tiktok-embed" cite="${r.full}" data-video-id="${videoId}" style="max-width: 605px; min-width: 325px;">
      <section><a target="_blank" href="${r.full}">${r.full}</a></section>
    </blockquote>`;
                }).join('\n');

                code = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TikTok Video Gallery</title>
  <style>
    body { font-family: sans-serif; background: #0b0f17; color: #fff; margin: 0; padding: 2rem; }
    .gallery { display: flex; flex-wrap: wrap; gap: 2rem; justify-content: center; }
  </style>
</head>
<body>
  <div class="gallery">
${embedsList}
  </div>
  <script async src="https://www.tiktok.com/embed.js"><\/script>
</body>
</html>`;

                previewHtml = `<div class="w-full space-y-4">
                    <p class="text-xs text-slate-400 italic mb-2">Standalone HTML Gallery View</p>
                    ${resolvedResults.map(r => {
                        const videoId = extractVideoId(r.full);
                        return `<div class="w-full flex justify-center bg-slate-900/50 p-2 rounded-xl border border-slate-800">
                            <blockquote class="tiktok-embed" cite="${r.full}" data-video-id="${videoId}" style="max-width: 100%; min-width: 280px;">
                                <section><a target="_blank" href="${r.full}">${r.full}</a></section>
                            </blockquote>
                        </div>`;
                    }).join('')}
                </div>`;
            }

            embedOutput.value = code;
            embedPreviewContainer.innerHTML = previewHtml;

            // Load official TikTok embed script if blockquotes are rendered
            if (type === 'official' || type === 'gridPage') {
                const existingScript = document.getElementById('tiktokEmbedScript');
                if (existingScript) existingScript.remove();
                
                const script = document.createElement('script');
                script.id = 'tiktokEmbedScript';
                script.src = 'https://www.tiktok.com/embed.js';
                script.async = true;
                document.body.appendChild(script);
            }
        }

        // Action: Download Complete Standalone HTML Page
        function downloadHtmlPage() {
            if (!embedOutput.value.trim()) {
                showToast('No embed code generated yet!', 'error');
                return;
            }

            const type = embedTypeSelect.value;
            let fullDoc = '';

            if (type === 'gridPage') {
                fullDoc = embedOutput.value;
            } else {
                fullDoc = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>TikTok Embed Gallery</title>
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: #0d1117; color: #f1f5f9; padding: 2rem; margin: 0; }
    .container { max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 1.5rem; justify-content: center; }
  </style>
</head>
<body>
  <div class="container">
    ${embedOutput.value}
  </div>
</body>
</html>`;
            }

            const blob = new Blob([fullDoc], { type: "text/html;charset=utf-8" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = `tiktok_embeds_${Date.now()}.html`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            showToast('Downloaded HTML page!', 'success');
        }
    </script>
</body>
</html>

Comments

Popular posts from this blog

Utk yg mo Bantu2 Keuangan saya
..monggo ke Bank Central Asia BCA 5520166779 a.n. Andreas Tparlaungan Manurung (Indonesia)


For those who would like to help support my finances
..please feel free to send it to Bank Central Asia (BCA) account number 5520166779 under the name Andreas Tparlaungan Manurung (Indonesia)

ANDREAS TOMMY PARLAUNGAN MANURUNG SHARED POOLING ACCOUNT MY ANDROID APKs PAGE please download here! REFRESH PAGE aka CHECK LATEST UPDATE! DOWNLOAD "SHOWING" POOL OF MY ANDROID-APK(s) aka APK CONTAINING LIST OF ALL MY ANDROID-APK(s) APP CLICK HERE FOR ALWAYS BEING UPDATED FOR MY LATEST APK! CONTOH HASIL "PROGRAM" App: Prompts' Guide aka TEMPLATE-HELPERs click here to download! Youtube and Instagram EMBEDded to Blogger/Blogspot.com SOURCE CODE Click this box to download 📥 TikTok EMBEDded to Blogger/Blogspot.com SOURCE CODE Input: BrowserLINK (mandatory) Click this box to download SHORTCUT-APPs note :  "precise" click to download R8: ronin1985.blogspot.com R2M: ronin-manu.blogspot.com Helping Download(ing) OnlineVIDEO! ...

[ERROR BUG]
ChatGPT+Gemini: TikTok → Blogger Embed Converter using Cloudflare/Online Server

🔄 Refresh Page ERROR BUG: The connection is blocked because it was initiated by a public page to connect to devices or servers on your local network. Planning: Revise Program CODE Code USING Javascript/Online Server Code NOT USING Javascript Sample Working Code aka Already Repaired! Temporary Solution is by Asking AI Assistant to do REPAIR CODE of (Not yet Repaired) Current Conversion Program Code-Output TikTok Archive – Embedded Preview TikTok Embed ▶ View this video on TikTok ⚠️ DISCLAIMER: INPUT URL LIMITATION This program is currently restricted to processing Full Browser URLs only. It does not support TikTok’s mobile "short-link" format (e.g., vt.tiktok.com ). Required Action: Users must open the video in a web browser and copy the expanded URL from the address bar before pasting it into this program. URL Conversion Example ❌ UNSUPPORTED: https://vt.tiktok.com/ZSaXoFyov/ ✅ REQ...

REPOST: Studying WATER PUMP by ROMAN ENGINEERING

*^ Ini yg Asli Gan! Mekanisme pada Concrete Pump: Kok ky Sistem Mekanik Romawi ya?! Tapi malah bisa HANYA pake PER aka bukan "MATA BOR look a like" Mekanisme Drill yg Cost Pembuatan bikin REPOT aka harus Tool SUPER Khusus Dari Material Besi yg digunakan terlihat langsung secara kasat mata Jauh Lebih Banyak drpd Per Biasa seperti yg ditunjukkan pd Video Alternatif dgn Penggunaan PER Video dr Instagram: Source: YouTube Rome's drainage machines #history #romanempire #engineering