您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在视频详情页追加视频封面链接
// ==UserScript== // @name Bilibili视频详情页追加视频封面链接 // @name:zh-CN Bilibili视频详情页追加视频封面链接 // @name:en Add video cover link to Bilibili video detail page // @name:ar إضافة رابط صورة غلاف الفيديو إلى صفحة تفاصيل فيديو Bilibili // @description:ar يعرض رابطًا مباشرًا لصورة غلاف الفيديو في صفحة تفاصيل الفيديو على Bilibili. // @name:bg Добавяне на връзка към обложката на видеото към страницата с подробности за видеоклипа в Bilibili // @description:bg Показва директна връзка към обложката на видеото на страницата с подробности за видеоклипа в Bilibili. // @name:cs Přidat odkaz na obálku videa na stránku s podrobnostmi o videu Bilibili // @description:cs Zobrazí přímý odkaz na obálku videa na stránce s podrobnostmi o videu na Bilibili. // @name:da Tilføj videocoverlink til Bilibili-videodetaljeside // @description:da Viser et direkte link til videocoveret på Bilibilis videodetaljeside. // @name:de Video-Cover-Link zur Bilibili-Videodetailseite hinzufügen // @description:de Zeigt einen direkten Link zum Video-Cover auf der Bilibili-Videodetailseite an. // @name:el Προσθήκη συνδέσμου εξωφύλλου βίντεο στη σελίδα λεπτομερειών βίντεο Bilibili // @description:el Εμφανίζει έναν άμεσο σύνδεσμο προς το εξώφυλλο βίντεο στη σελίδα λεπτομερειών βίντεο του Bilibili. // @name:eo Aldoni videokovrilan ligilon al Bilibili-videa detala paĝo // @description:eo Montras rektan ligilon al la videokovrilo sur la Bilibili-videa detala paĝo. // @name:es Agregar enlace de portada de video a la página de detalles del video de Bilibili // @description:es Muestra un enlace directo a la portada del video en la página de detalles del video de Bilibili. // @name:fi Lisää videon kansikuvalinkki Bilibilin videotietosivulle // @description:fi Näyttää suoran linkin videon kansikuvaan Bilibilin videotietosivulla. // @name:fr Ajouter un lien de couverture vidéo à la page de détails de la vidéo Bilibili // @description:fr Affiche un lien direct vers la couverture de la vidéo sur la page de détails de la vidéo Bilibili. // @name:fr-CA Ajouter un lien de couverture vidéo à la page de détails de la vidéo Bilibili // @description:fr-CA Affiche un lien direct vers la couverture de la vidéo sur la page de détails de la vidéo Bilibili. // @name:he הוסף קישור לעטיפת וידאו לדף הפרטים של סרטון Bilibili // @description:he מציג קישור ישיר לעטיפת הוידאו בדף הפירוט של סרטון בביליבילי. // @name:hr Dodaj vezu naslovnice videa na stranicu s detaljima videozapisa Bilibili // @description:hr Prikazuje izravnu vezu na naslovnicu videa na stranici s detaljima videozapisa na Bilibili. // @name:hu Videóborító link hozzáadása a Bilibili videó részletező oldalához // @description:hu Közvetlen linket jelenít meg a videó borítójához a Bilibili videó részletező oldalán. // @name:id Tambahkan tautan sampul video ke halaman detail video Bilibili // @description:id Menampilkan tautan langsung ke sampul video di halaman detail video Bilibili. // @name:it Aggiungi link di copertina video alla pagina dei dettagli del video di Bilibili // @description:it Mostra un link diretto alla copertina del video nella pagina dei dettagli del video di Bilibili. // @name:ja Bilibiliビデオ詳細ページにビデオカバーリンクを追加 // @description:ja Bilibiliのビデオ詳細ページにビデオカバーへの直接リンクを表示します。 // @name:ka Bilibili ვიდეოს დეტალური გვერდზე ვიდეოს ყდის ბმულის დამატება // @description:ka აჩვენებს ვიდეოს ყდის პირდაპირ ბმულს Bilibili-ის ვიდეოს დეტალურ გვერდზე. // @name:ko Bilibili 비디오 세부 정보 페이지에 비디오 커버 링크 추가 // @description:ko Bilibili 비디오 세부 정보 페이지에서 비디오 커버에 대한 직접 링크를 표시합니다. // @name:nb Legg til videocoverlenke til Bilibili videodetaljside // @description:nb Viser en direkte lenke til videocoveret på Bilibili videodetaljside. // @name:nl Voeg een video-coverlink toe aan de Bilibili-videodetailpagina // @description:nl Toont een directe link naar de video-cover op de Bilibili-videodetailpagina. // @name:pl Dodaj link do okładki wideo na stronie szczegółów filmu Bilibili // @description:pl Wyświetla bezpośredni link do okładki wideo na stronie szczegółów filmu Bilibili. // @name:pt-BR Adicionar link da capa do vídeo à página de detalhes do vídeo Bilibili // @description:pt-BR Exibe um link direto para a capa do vídeo na página de detalhes do vídeo no Bilibili. // @name:ro Adăugați un link de copertă video la pagina de detalii video Bilibili // @description:ro Afișează un link direct către coperta video pe pagina de detalii video Bilibili. // @name:ru Добавить ссылку на обложку видео на страницу сведений о видео Bilibili // @description:ru Отображает прямую ссылку на обложку видео на странице сведений о видео Bilibili. // @name:sk Pridať odkaz na obálku videa na stránku s podrobnosťami o videu Bilibili // @description:sk Zobrazuje priamy odkaz na obálku videa na stránke s podrobnosťami o videu na Bilibili. // @name:sr Додај везу омота видео снимка на страницу са детаљима видео снимка Bilibili // @description:sr Приказује директну везу до омота видео снимка на страници са детаљима видео снимка на Bilibili. // @name:sv Lägg till videocoverlänk till Bilibili videodetaljsida // @description:sv Visar en direktlänk till videocoveret på Bilibili videodetaljsida. // @name:th เพิ่มลิงก์หน้าปกวิดีโอไปยังหน้าข้อมูลวิดีโอ Bilibili // @description:th แสดงลิงก์ตรงไปยังหน้าปกวิดีโอบนหน้าข้อมูลวิดีโอของ Bilibili // @name:tr Bilibili video ayrıntı sayfasına video kapak bağlantısı ekle // @description:tr Bilibili video ayrıntı sayfasında video kapağına doğrudan bağlantı görüntüler. // @name:ug Bilibili سىن تەپسىلىي بېتىگە سىن مۇقاۋىسى ئۇلىنىشى قوشۇڭ // @description:ug Bilibili سىن تەپسىلىي بېتىدە سىن مۇقاۋىسىغا بىۋاسىتە ئۇلىنىش كۆرسىتىدۇ. // @name:uk Додати посилання на обкладинку відео на сторінку відомостей про відео Bilibili // @description:uk Відображає пряме посилання на обкладинку відео на сторінці відомостей про відео Bilibili. // @name:vi Thêm liên kết bìa video vào trang chi tiết video Bilibili // @description:vi Hiển thị một liên kết trực tiếp đến ảnh bìa video trên trang chi tiết video Bilibili. // @name:zh Bilibili视频详情页追加视频封面链接 // @description:zh 在Bilibili视频详情页面显示视频封面图片的直接链接。 // @description:zh-CN 在Bilibili视频详情页面显示视频封面图片的直接链接。 // @name:zh-HK Bilibili視頻詳情頁追加視頻封面鏈接 // @description:zh-HK 在Bilibili視頻詳情頁面顯示視頻封面圖片的直接鏈接。 // @name:zh-SG Bilibili视频详情页追加视频封面链接 // @description:zh-SG 在Bilibili视频详情页面显示视频封面图片的直接链接。 // @name:zh-TW Bilibili視頻詳情頁追加視頻封面鏈接 // @description:zh-TW 在Bilibili視頻詳情頁面顯示視頻封面圖片的直接鏈接。 // @namespace http://tampermonkey.net/ // @version 0.1.0 // @description 在视频详情页追加视频封面链接 // @description:en Add video cover link to video detail page // @namespace http://tampermonkey.net/ // @description Adds a link to the video cover in the info section and displays the cover image below the recommendations footer on Bilibili video pages. // @author aspen138 // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_notification // @grant window.onurlchange // @license MIT // ==/UserScript== // Acknowledgement: Gemini 2.5 Pro 03-25 (function() { 'use strict'; // ↓↓↓↓↓↓↓↓↓模板,建议直接复制 // // 自定义 urlchange 事件(用来监听 URL 变化) function addUrlChangeEvent() { if (window.onurlchange === undefined) { // Only add if Tampermonkey hasn't provided it history.pushState = ( f => function pushState(){ var ret = f.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); window.dispatchEvent(new Event('urlchange')); return ret; })(history.pushState); history.replaceState = ( f => function replaceState(){ var ret = f.apply(this, arguments); window.dispatchEvent(new Event('replacestate')); window.dispatchEvent(new Event('urlchange')); return ret; })(history.replaceState); window.addEventListener('popstate',()=>{ window.dispatchEvent(new Event('urlchange')) }); console.log("Custom 'urlchange' event listener added."); } else { console.log("Using built-in 'urlchange' event listener."); } } var menu_ALL = [ ['menu_isEnableAppendCover', '添加视频封面链接和图片', '添加视频封面链接和图片功能', true] // Default changed to true for convenience ], menu_ID = []; // Initialize default values for (let i=0; i<menu_ALL.length; i++){ if (GM_getValue(menu_ALL[i][0]) === null){ // Use strict comparison for null GM_setValue(menu_ALL[i][0], menu_ALL[i][3]); } } // 注册脚本菜单 function registerMenuCommand() { // Clear existing menus before re-registering for (let i = 0; i < menu_ID.length; i++) { if (menu_ID[i]) { // Check if ID exists before trying to unregister try { GM_unregisterMenuCommand(menu_ID[i]); } catch (e) { console.warn("Could not unregister menu command:", menu_ID[i], e); } } } menu_ID = []; // Reset the array for (let i = 0; i < menu_ALL.length; i++) { menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]); // Update current status from storage const isEnabled = menu_ALL[i][3]; const commandLabel = `${isEnabled ? '✅' : '❌'} ${menu_ALL[i][1]}`; const menuName = menu_ALL[i][0]; const menuTips = menu_ALL[i][2]; // Use a closure to capture the correct variables for the callback (function(currentStatus, name, tips) { menu_ID[i] = GM_registerMenuCommand(commandLabel, function() { menu_switch(currentStatus, name, tips); }); })(isEnabled, menuName, menuTips); } } // 菜单开关 function menu_switch(menu_status, Name, Tips) { const newState = !menu_status; // Toggle the state GM_setValue(Name, newState); GM_notification({ text: `已${newState ? '开启' : '关闭'} [${Tips}] 功能\n(刷新网页后生效)`, // Simplified message timeout: 3500, onclick: function(){ location.reload(); } }); registerMenuCommand(); // Update menu state immediately }; // ↑↑↑↑↑↑↑↑↑↑↑↑模板,建议直接复制 // // --- Script Core Logic --- const INFO_CONTAINER_SELECTOR = '.video-info-detail-list.video-info-detail-content'; const FOOTER_SELECTOR = '.rec-footer[data-v-17ce950e]'; // More specific selector if needed const COVER_LINK_CLASS = 'bili-cover-link-item'; // Custom class for the link container const COVER_IMAGE_CONTAINER_CLASS = 'bili-cover-image-container'; // Custom class for the image container let currentBVid = null; // Keep track of the current video ID let checkInterval = null; // Interval timer handle const CHECK_INTERVAL_MS = 500; // Check every 500ms const MAX_CHECKS = 40; // Try for 20 seconds max // Main function to add cover link and image function addCoverElements() { const infoContainer = document.querySelector(INFO_CONTAINER_SELECTOR); const recFooter = document.querySelector(FOOTER_SELECTOR); const imageMetaTag = document.head.querySelector('meta[itemprop="image"]'); // Check if all necessary elements are present if (!infoContainer || !recFooter || !imageMetaTag) { // console.log('Waiting for elements...'); return false; // Indicate elements are not ready } // Check if we've already processed this specific info container if (infoContainer.dataset.coverProcessed === 'true') { // console.log('Already processed this container.'); return true; // Indicate processing is done or already happened } // Extract cover URL const coverImgUrlRaw = imageMetaTag.getAttribute('content'); if (!coverImgUrlRaw) { console.warn('Cover image meta tag found, but content is empty.'); return false; // Cannot proceed without URL } const coverImgUrl = 'https://' + coverImgUrlRaw.replace(/^https?:?\/\//, '').split('@')[0]; console.log("Cover URL found:", coverImgUrl); // --- 1. Add the Cover Link to Info Section --- // Check if link already exists (belt-and-suspenders check) if (!infoContainer.querySelector(`.${COVER_LINK_CLASS}`)) { const coverItem = document.createElement('div'); coverItem.classList.add(COVER_LINK_CLASS, 'item'); // Add custom class and 'item' class // Optional: Add an icon (simplified) const coverIcon = document.createElement('span'); coverIcon.textContent = '🖼️'; // Emoji icon coverIcon.style.marginRight = '5px'; coverIcon.style.fontSize = '16px'; coverIcon.style.verticalAlign = 'middle'; const coverLink = document.createElement('a'); coverLink.href = coverImgUrl; coverLink.target = '_blank'; coverLink.rel = 'noopener noreferrer'; coverLink.title = '点击查看封面原图 (Click to view original cover)'; coverLink.style.verticalAlign = 'middle'; const linkText = document.createElement('span'); linkText.textContent = '封面 (Cover)'; coverLink.appendChild(coverIcon); coverLink.appendChild(linkText); coverItem.appendChild(coverLink); infoContainer.appendChild(coverItem); // Append the new item console.log('Cover link appended to info section.'); } // --- 2. Add the Cover Image below the Footer --- // Check if image container already exists as the next sibling of the footer if (!recFooter.nextElementSibling || !recFooter.nextElementSibling.classList.contains(COVER_IMAGE_CONTAINER_CLASS)) { const imageContainer = document.createElement('div'); imageContainer.className = COVER_IMAGE_CONTAINER_CLASS; imageContainer.style.marginTop = '15px'; // Add some space above the image imageContainer.style.textAlign = 'center'; // Center the image container const coverImage = document.createElement('img'); coverImage.src = coverImgUrl; coverImage.alt = 'Video Cover Image'; coverImage.style.maxWidth = '100%'; // Ensure image is responsive coverImage.style.height = 'auto'; coverImage.style.borderRadius = '4px'; // Optional: slightly rounded corners coverImage.style.cursor = 'pointer'; // Indicate it's clickable coverImage.title = '点击查看封面原图 (Click to view original cover)'; // Make the image itself a link to the cover const imageLink = document.createElement('a'); imageLink.href = coverImgUrl; imageLink.target = '_blank'; imageLink.rel = 'noopener noreferrer'; imageLink.appendChild(coverImage); imageContainer.appendChild(imageLink); // Insert the container *after* the footer element recFooter.parentNode.insertBefore(imageContainer, recFooter.nextSibling); console.log('Cover image appended below footer.'); } // Mark the info container as processed to prevent duplicates if this function runs again infoContainer.dataset.coverProcessed = 'true'; return true; // Indicate success } // Function to start the process of adding elements function runLogic() { if (!GM_getValue('menu_isEnableAppendCover', true)) { console.log('Cover feature is disabled.'); return; // Exit if the feature is turned off } const newBVid = location.pathname.match(/BV[^/]+/)?.[0]; if (!newBVid) { // console.log("Not a video page or BVid not found."); return; } // Only reset and run if the BVid changed or it's the first time if (newBVid !== currentBVid) { console.log(`New video detected (BVid: ${newBVid}). Running cover logic.`); currentBVid = newBVid; // Clear any previous interval if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } // Reset processed flags on potential old elements (though ideally elements are replaced on navigation) const oldInfo = document.querySelector(`${INFO_CONTAINER_SELECTOR}[data-cover-processed="true"]`); if(oldInfo) delete oldInfo.dataset.coverProcessed; const oldImage = document.querySelector(`.${COVER_IMAGE_CONTAINER_CLASS}`); if(oldImage) oldImage.remove(); let checks = 0; checkInterval = setInterval(() => { checks++; // console.log(`Check ${checks}/${MAX_CHECKS}`); try { if (addCoverElements() || checks >= MAX_CHECKS) { clearInterval(checkInterval); checkInterval = null; if (checks >= MAX_CHECKS) { console.warn("Cover script timed out waiting for elements."); } else { console.log("Cover elements added successfully or already present."); } } } catch (error) { console.error("Error during cover element check/addition:", error); clearInterval(checkInterval); // Stop on error checkInterval = null; } }, CHECK_INTERVAL_MS); } else { // console.log("Same video page (BVid: " + currentBVid + "), no immediate action needed unless elements reappear."); // Optional: Could add a check here to see if elements disappeared and need re-adding, // but usually page navigation handles this. } } // --- Initialization --- addUrlChangeEvent(); // Ensure URL change listener is set up registerMenuCommand(); // Set up the script menu // Run the logic on initial load // Use DOMContentLoaded or a small delay to ensure basic structure exists if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', runLogic); } else { // Small delay in case runLogic depends on elements added dynamically shortly after load setTimeout(runLogic, 200); } // Run the logic whenever the URL changes window.addEventListener('urlchange', () => { console.log("URL change detected by script."); // Use a small delay to allow the page content to potentially update setTimeout(runLogic, 500); // Delay slightly after URL change }); })();