在视频详情页追加视频封面链接
// ==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
});
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址