抖音视频信息获取 /* Custom scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #555; } /* Animation */ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .fade-in { animation: fadeIn 0.3s ease-out forwards; } /* Custom select arrow */ .custom-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%236b7280'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; } /* Image hover effect */ .image-container:hover img { transform: scale(1.03); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } /* Button loading state */ .button-loading { position: relative; pointer-events: none; } .button-loading::after { content: ""; position: absolute; width: 16px; height: 16px; top: 0; left: 0; right: 0; bottom: 0; margin: auto; border: 3px solid transparent; border-top-color: white; border-radius: 50%; animation: button-loading-spinner 1s ease infinite; } @keyframes button-loading-spinner { from { transform: rotate(0turn); } to { transform: rotate(1turn); } } /* Video player styling */ #video-player { display: block; margin: 0 auto; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); max-width: 100%; height: auto; max-height: calc(100vh - 100px); /* Limit height for better layout */ } /* Smooth transitions */ .transition-all { transition: all 0.2s ease; } /* Title clamping and expand/collapse */ .title-clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; /* Limit to 3 lines */ overflow: hidden; text-overflow: ellipsis; } .title-clamp.expanded { -webkit-line-clamp: unset; /* Show full content when expanded */ overflow: visible; text-overflow: unset; } .expand-button { cursor: pointer; color: #1d4ed8; /* blue-700 */ font-size: 0.875rem; /* text-sm */ margin-top: 0.5rem; display: inline-block; } 参数设置 API线路 默认线路 线路1 线路2 线路3 ID Key 抖音视频链接 粘贴链接 获取信息 历史记录 暂无历史记录 内容将显示在这里 请输入抖音链接并点击"获取信息" // 预设的 ID 和 Key const defaultId = "10003120"; const defaultKey = "bfb75036b210ed35bee6e002d623293c"; // 从 localStorage 加载 ID 和 Key,如果 localStorage 中没有,则使用预设值 const idInput = document.getElementById("id"); const keyInput = document.getElementById("key"); const fetchBtn = document.getElementById("fetch-btn"); idInput.value = localStorage.getItem("id") || defaultId; keyInput.value = localStorage.getItem("key") || defaultKey; //API 线路 const apiRoutes = [ "https://cn.apihz.cn/api/fun/douyin.php", "http://101.35.2.25/api/fun/douyin.php", "http://124.222.204.22/api/fun/douyin.php", "http://124.220.49.230/api/fun/douyin.php" ]; // 从 localStorage 加载 API 线路 const storedRoute = localStorage.getItem("apiRoute") || "0"; // 默认线路 document.getElementById("api-route").value = storedRoute; let history = JSON.parse(localStorage.getItem("douyinHistory") || "[]"); displayHistory(); function getCurrentApiUrl() { const routeIndex = parseInt(document.getElementById("api-route").value); return apiRoutes[routeIndex] || apiRoutes[0]; // 默认使用第一个线路 } function fetchData() { const id = document.getElementById("id").value; const key = document.getElementById("key").value; let douyinUrl = document.getElementById("douyin-url").value; const selectedRoute = document.getElementById("api-route").value; // 保存 API 线路 到 localStorage localStorage.setItem("apiRoute", selectedRoute); if (!id || !key) { showAlert("请输入 ID 和 Key!", "error"); return; } if (!douyinUrl) { showAlert("请输入抖音视频链接!", "error"); return; } // 提取抖音链接 if (!douyinUrl.startsWith("https://")) { const match = douyinUrl.match(/https:\/\/v\.douyin\.com\/[^\s]+/); if (match) { douyinUrl = match[0]; // 找到最后一个 / 的位置 const lastSlashIndex = douyinUrl.lastIndexOf('/'); // 提取链接到最后一个 / douyinUrl = douyinUrl.substring(0, lastSlashIndex + 1); } else { showAlert("无法从输入中提取有效的抖音链接.", "error"); return; } } // 保存 ID 和 Key 到 localStorage localStorage.setItem("id", id); localStorage.setItem("key", key); // Show loading state const originalButtonContent = fetchBtn.innerHTML; // Save original content fetchBtn.innerHTML = ''; // Clear content fetchBtn.classList.add('button-loading'); fetchBtn.disabled = true; // 尝试所有的 API 线路 tryFetchData(0, id, key, douyinUrl, originalButtonContent); // Pass original content } function tryFetchData(routeIndex, id, key, douyinUrl, originalButtonContent) { // Receive original content if (routeIndex >= apiRoutes.length) { // 所有线路都失败了 document.getElementById("info-container").innerHTML = ` <div class="text-center py-8 text-red-500"> <i class="fas fa-exclamation-triangle text-3xl mb-2"></i> <p class="text-lg">Tất cả yêu cầu dòng đều không thành công, vui lòng thử lại sau!</p> </div> `; document.getElementById("content-container").innerHTML = ` <div class="flex flex-col items-center justify-center h-32 text-gray-400"> <i class="fas fa-photo-video text-4xl mb-2"></i> <p>Nội dung sẽ xuất hiện ở đây</p> </div> `; // Reset button fetchBtn.innerHTML = originalButtonContent; // Restore original content fetchBtn.classList.remove('button-loading'); fetchBtn.disabled = false; return; } const apiUrl = `${apiRoutes[routeIndex]}?id=${id}&key=${key}&url=${douyinUrl}`; fetch(apiUrl) .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { if (data.code === 200) { addToHistory(douyinUrl, data); } displayData(data); // Reset button fetchBtn.innerHTML = originalButtonContent; // Restore original content fetchBtn.classList.remove('button-loading'); fetchBtn.disabled = false; }) .catch(error => { console.error(`Error fetching data from route ${routeIndex + 1}:`, error); // 切换到下一个线路 console.log(`尝试切换到下一个线路: ${routeIndex + 2}`); tryFetchData(routeIndex + 1, id, key, douyinUrl, originalButtonContent); // Pass original content }); } function displayData(data) { const infoContainer = document.getElementById("info-container"); const contentContainer = document.getElementById("content-container"); infoContainer.innerHTML = ""; contentContainer.innerHTML = ""; if (data.code !== 200) { infoContainer.innerHTML = ` <div class="text-center py-8 text-red-500 fade-in"> <i class="fas fa-exclamation-triangle text-3xl mb-2"></i> <p class="text-lg">Error: ${data.code}</p> </div> `; return; } // Create info container with fade-in animation const infoDiv = document.createElement('div'); infoDiv.className = 'fade-in'; // Display Author, Title infoDiv.innerHTML = ` <div class="flex flex-col gap-6"> <div> <img src="${data.cover}" alt="封面" class="w-full h-auto rounded-lg shadow-md"> </div> <div> <div> <h3 class="text-xl font-semibold text-gray-800 mb-2 flex items-center"> <i class="fas fa-user text-blue-500 mr-2"></i> 作者: ${data.name} </h3> <p class="text-gray-700"> <i class="fas fa-heading text-purple-500 mr-2"></i> 标题: <span id="video-title" class="title-clamp">${data.title}</span> <span id="expand-button" class="expand-button" onclick="toggleTitleExpansion()">展开</span> </p> </div> `; if (data.type === "视频") { // 创建下载链接 infoDiv.innerHTML += ` <div class="mt-4 flex flex-row gap-4"> <div> <button onclick="copyToClipboard('${data.yvideo}')" class="px-3 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg text-sm flex items-center transition-all"> <i class="fas fa-copy mr-2"></i> 复制链接 </button> </div> <div> <a href="/api/download?url=${encodeURIComponent(data.yvideo)}" target="_blank" class="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm inline-flex items-center transition-all"> <i class="fas fa-download mr-2"></i> 下载视频 </a> </div> </div> `; contentContainer.innerHTML = ` <div class="fade-in"> <h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center"> <i class="fas fa-play-circle text-red-500 mr-2"></i> 视频预览 </h3> <video id="video-player" controls> <source src="/api/video?url=${encodeURIComponent(data.yvideo)}" type="video/mp4"> 您的浏览器不支持 HTML5 视频。 </video> </div> `; } else if (data.type === "图集") { infoDiv.innerHTML += ` <div class="mt-4"> <div> <button onclick="copyToClipboard('${data.images[0]}')" class="px-3 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg text-sm flex items-center transition-all"> <i class="fas fa-copy mr-2"></i> 复制封面链接 </button> </div> </div> `; // Use DOM manipulation to build the content container for image sets const imageContentDiv = document.createElement('div'); imageContentDiv.className = 'fade-in'; imageContentDiv.innerHTML = ` <h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center"> <i class="fas fa-images text-green-500 mr-2"></i> 图片预览 (${data.images.length}张) </h3> `; // Container for filename input and download all button const downloadAllContainer = document.createElement("div"); downloadAllContainer.className = "flex flex-col sm:flex-row items-center gap-3 mb-4 bg-gray-50 p-3 rounded-lg"; const filenameInput = document.createElement("input"); filenameInput.type = "text"; filenameInput.id = "filename-input"; filenameInput.placeholder = "输入文件名"; filenameInput.className = "flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"; filenameInput.value = "抖音图片"; // Default filename downloadAllContainer.appendChild(filenameInput); const downloadAllButton = document.createElement("button"); downloadAllButton.textContent = "一键下载所有图片"; downloadAllButton.className = "px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center transition-all whitespace-nowrap"; downloadAllButton.innerHTML = '<i class="fas fa-download mr-2"></i> 一键下载所有图片'; // Add icon downloadAllButton.addEventListener('click', () => { const filename = document.getElementById("filename-input").value || "douyin_images"; // Get filename from input downloadAllImages(data.images, filename); }); downloadAllContainer.appendChild(downloadAllButton); imageContentDiv.appendChild(downloadAllContainer); // Append to imageContentDiv // Create the image grid container const imageGrid = document.createElement("div"); imageGrid.className = "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4"; // Keep Tailwind grid classes // Add event listener for individual image downloads using event delegation imageGrid.addEventListener('click', function(event) { if (event.target.closest('.download-icon')) { event.preventDefault(); // Prevent the link from navigating const downloadLink = event.target.closest('.download-icon'); const imageUrl = downloadLink.dataset.url; const baseFilename = document.getElementById('filename-input').value || 'douyin_image'; // Get filename from input const index = Array.from(imageGrid.children).indexOf(downloadLink.closest('.image-container')); // Get the index of the image const filename = `${baseFilename}_${index + 1}.jpg`; // Construct filename downloadResource(imageUrl, filename); // Use the existing downloadResource function } }); // Display Images in the grid data.images.forEach((imageUrl, index) => { const imageContainer = document.createElement("div"); imageContainer.className = "image-container relative group rounded-lg overflow-hidden transition-all"; // Keep existing styling classes imageContainer.innerHTML = ` <img src="${imageUrl}" alt="图片 ${index + 1}" class="w-full h-auto object-cover transition-all"> <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-all"></div> <a class="download-icon absolute top-2 right-2 bg-black bg-opacity-70 text-white p-2 rounded-full opacity-0 group-hover:opacity-100 transition-all" href="#" data-url="${imageUrl}" data-filename="image${index + 1}.jpg"> <i class="fas fa-download text-sm"></i> </a> `; imageGrid.appendChild(imageContainer); }); // Append the grid to the image content div, then append the div to the content container imageContentDiv.appendChild(imageGrid); contentContainer.appendChild(imageContentDiv); } else { contentContainer.innerHTML = ` <div class="fade-in text-center py-8 text-gray-500"> <i class="fas fa-info-circle text-3xl mb-2"></i> <p class="text-lg">类型: ${data.type}</p> </div> `; } infoDiv.innerHTML += ` </div> </div> `; infoContainer.appendChild(infoDiv); } function showAlert(message, type) { const alertDiv = document.createElement('div'); alertDiv.className = `fixed top-4 right-4 px-4 py-3 rounded-lg shadow-lg z-50 fade-in ${type === 'error' ? 'bg-red-100 text-red-700' : 'bg-green-100 text-green-700'}`; alertDiv.innerHTML = ` <div class="flex items-center"> <i class="fas ${type === 'error' ? 'fa-exclamation-circle' : 'fa-check-circle'} mr-2"></i> <span>${message}</span> </div> `; document.body.appendChild(alertDiv); setTimeout(() => { alertDiv.classList.remove('fade-in'); alertDiv.classList.add('opacity-0', 'transition-all'); setTimeout(() => alertDiv.remove(), 300); }, 3000); } function copyToClipboard(text) { navigator.clipboard.writeText(text) .then(() => { showAlert("链接已复制到剪贴板!", "success"); }) .catch(err => { console.error('复制失败: ', err); showAlert("复制失败,请手动复制!", "error"); }); } function pasteUrl() { navigator.clipboard.readText().then(text => { document.getElementById('douyin-url').value = text; showAlert("已从剪贴板粘贴链接!", "success"); }).catch(err => { console.error('无法读取剪贴板内容: ', err); showAlert("无法读取剪贴板内容!", "error"); }); } function downloadResource(url, filename) { fetch(url, { mode: 'cors', redirect: 'follow' }) .then(response => { if (!response.ok) { throw new Error(`Network response was not ok: ${response.status}`); } return response.blob(); }) .then(blob => { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); URL.revokeObjectURL(a.href); document.body.removeChild(a); showAlert("下载已开始!", "success"); }) .catch(error => { console.error('Download error:', error); showAlert("下载失败!", "error"); }); } function downloadAllImages(imageUrls, baseFilename) { showAlert(`开始下载 ${imageUrls.length} 张图片...`, "success"); imageUrls.forEach((imageUrl, index) => { setTimeout(() => { downloadResource(imageUrl, `${baseFilename}_${index + 1}.jpg`); }, index * 500); // Stagger downloads to avoid browser blocking }); } function addToHistory(url, data) { const existingIndex = history.findIndex(item => item.url === url); if (existingIndex !== -1) { history.splice(existingIndex, 1); // Remove old entry if it exists } history.unshift({ url: url, data: data }); if (history.length > 10) { history.pop(); // Limit to 10 entries } localStorage.setItem("douyinHistory", JSON.stringify(history)); displayHistory(); } function displayHistory() { const historyList = document.getElementById("history-list"); historyList.innerHTML = ""; if (history.length === 0) { historyList.innerHTML = ` <li class="py-3 text-center text-gray-400"> <i class="fas fa-clock mr-2"></i>暂无历史记录 </li> `; return; } history.forEach((item, index) => { const listItem = document.createElement("li"); listItem.className = "py-3 flex items-center justify-between group fade-in"; listItem.innerHTML = ` <a href="#" onclick="loadHistoryItem(${index})" class="text-blue-600 hover:text-blue-800 hover:underline flex-1 truncate pr-2"> <i class="fas fa-link mr-2 text-gray-400"></i>${item.url} </a> <button onclick="deleteHistoryItem(${index})" class="text-red-500 hover:text-red-700 opacity-0 group-hover:opacity-100 transition-all"> <i class="fas fa-trash-alt"></i> </button> `; historyList.appendChild(listItem); }); } function loadHistoryItem(index) { const item = history[index]; document.getElementById("douyin-url").value = item.url; displayData(item.data); // Scroll to bottom after delay setTimeout(() => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }, 200); // 0.2 seconds delay showAlert("已加载历史记录!", "success"); } function deleteHistoryItem(index) { history.splice(index, 1); localStorage.setItem("douyinHistory", JSON.stringify(history)); displayHistory(); // Clear the data currently displayed if it matches the deleted history document.getElementById("info-container").innerHTML = ` <div class="flex flex-col items-center justify-center h-full text-gray-400"> <i class="fas fa-info-circle text-4xl mb-2"></i> <p>请输入抖音链接并点击"获取信息"</p> </div> `; document.getElementById("content-container").innerHTML = ` <div class="flex flex-col items-center justify-center h-32 text-gray-400"> <i class="fas fa-photo-video text-4xl mb-2"></i> <p>内容将显示在这里</p> </div> `; document.getElementById("douyin-url").value = ""; showAlert("已删除历史记录!", "success"); } function toggleTitleExpansion() { const titleSpan = document.getElementById('video-title'); const expandButton = document.getElementById('expand-button'); if (titleSpan.classList.contains('expanded')) { titleSpan.classList.remove('expanded'); expandButton.textContent = '展开'; } else { titleSpan.classList.add('expanded'); expandButton.textContent = '收起'; } }