Greasy Fork is available in English.
YouTube 首页/搜索分段缩略图网格100%修复,Gemini一键总结/字幕
当前为
// ==UserScript==
// @name YouTube to Gemini 自动总结与字幕
// @namespace http://tampermonkey.net/
// @version 2.0
// @description YouTube 首页/搜索分段缩略图网格100%修复,Gemini一键总结/字幕
// @author hengyu (优化 by Assistant)
// @match *://www.youtube.com/*
// @match *://gemini.google.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- 终极分段网格修复 CSS ---
// 只对首页和搜索结果页面应用网格布局修复
GM_addStyle(`
/* 首页和搜索页面网格布局 */
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 24px 16px !important;
width: 100% !important;
margin: 0 auto !important;
--ytd-rich-grid-items-per-row: 2 !important;
--ytd-rich-grid-max-width: none !important;
}
@media (min-width: 1000px) {
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents {
grid-template-columns: repeat(3, 1fr) !important;
--ytd-rich-grid-items-per-row: 3 !important;
}
}
@media (min-width: 1400px) {
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents {
grid-template-columns: repeat(4, 1fr) !important;
--ytd-rich-grid-items-per-row: 4 !important;
}
}
@media (min-width: 1700px) {
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents {
grid-template-columns: repeat(5, 1fr) !important;
--ytd-rich-grid-items-per-row: 5 !important;
}
}
/* 确保只在首页和搜索页面修改布局结构 */
body[data-is-home-page="true"] ytd-rich-grid-row,
body[data-is-home-page="true"] ytd-rich-grid-row > #contents,
body[data-is-home-page="true"] ytd-rich-grid-row > #dismissible,
body[data-is-home-page="true"] ytd-rich-grid-row > div,
body[data-is-home-page="true"] ytd-rich-grid-row > #dismissible > #contents,
body[data-is-home-page="true"] ytd-rich-grid-row > div > #contents,
body[data-is-home-page="true"] ytd-rich-grid-row > div > #dismissible,
body[data-is-home-page="true"] ytd-rich-grid-row > div > #dismissible > #contents,
body[data-is-home-page="true"] ytd-rich-grid-row > .ytd-rich-grid-row,
body[data-is-home-page="true"] ytd-rich-grid-row > div > .ytd-rich-grid-row,
body[data-is-home-page="true"] ytd-rich-grid-row > div > div,
body[data-is-home-page="true"] ytd-rich-grid-row > div > div > #contents,
body[data-page-subtype="home"] ytd-rich-grid-row,
body[data-page-subtype="home"] ytd-rich-grid-row > #contents,
body[data-page-subtype="home"] ytd-rich-grid-row > #dismissible,
body[data-page-subtype="home"] ytd-rich-grid-row > div,
body[data-page-subtype="home"] ytd-rich-grid-row > #dismissible > #contents,
body[data-page-subtype="home"] ytd-rich-grid-row > div > #contents,
body[data-page-subtype="home"] ytd-rich-grid-row > div > #dismissible,
body[data-page-subtype="home"] ytd-rich-grid-row > div > #dismissible > #contents,
body[data-page-subtype="home"] ytd-rich-grid-row > .ytd-rich-grid-row,
body[data-page-subtype="home"] ytd-rich-grid-row > div > .ytd-rich-grid-row,
body[data-page-subtype="home"] ytd-rich-grid-row > div > div,
body[data-page-subtype="home"] ytd-rich-grid-row > div > div > #contents,
body[data-page-type="search"] ytd-rich-grid-row,
body[data-page-type="search"] ytd-rich-grid-row > #contents,
body[data-page-type="search"] ytd-rich-grid-row > #dismissible,
body[data-page-type="search"] ytd-rich-grid-row > div,
body[data-page-type="search"] ytd-rich-grid-row > #dismissible > #contents,
body[data-page-type="search"] ytd-rich-grid-row > div > #contents,
body[data-page-type="search"] ytd-rich-grid-row > div > #dismissible,
body[data-page-type="search"] ytd-rich-grid-row > div > #dismissible > #contents,
body[data-page-type="search"] ytd-rich-grid-row > .ytd-rich-grid-row,
body[data-page-type="search"] ytd-rich-grid-row > div > .ytd-rich-grid-row,
body[data-page-type="search"] ytd-rich-grid-row > div > div,
body[data-page-type="search"] ytd-rich-grid-row > div > div > #contents {
display: contents !important;
}
/* 视频项修复 - 仅限首页和搜索页面 */
body[data-is-home-page="true"] ytd-rich-item-renderer,
body[data-is-home-page="true"] ytd-grid-video-renderer,
body[data-is-home-page="true"] ytd-rich-grid-media,
body[data-page-subtype="home"] ytd-rich-item-renderer,
body[data-page-subtype="home"] ytd-grid-video-renderer,
body[data-page-subtype="home"] ytd-rich-grid-media,
body[data-page-type="search"] ytd-rich-item-renderer,
body[data-page-type="search"] ytd-grid-video-renderer,
body[data-page-type="search"] ytd-rich-grid-media {
width: 100% !important;
max-width: none !important;
min-width: 0 !important;
margin: 0 !important;
box-sizing: border-box !important;
}
/* 弹性项 - 仅首页和搜索页面 */
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents > ytd-rich-section-renderer,
body[data-is-home-page="true"] ytd-rich-grid-renderer > #contents > ytd-reel-shelf-renderer,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents > ytd-rich-section-renderer,
body[data-page-subtype="home"] ytd-rich-grid-renderer > #contents > ytd-reel-shelf-renderer,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents > ytd-rich-section-renderer,
body[data-page-type="search"] ytd-rich-grid-renderer > #contents > ytd-reel-shelf-renderer {
grid-column: 1 / -1 !important;
width: 100% !important;
margin: 16px 0 !important;
}
/* 搜索页面修复 */
ytd-search ytd-video-renderer {
display: block !important;
position: relative !important;
z-index: 1 !important;
}
ytd-search ytd-thumbnail {
position: relative !important;
z-index: 5 !important;
}
`);
// --- Gemini 按钮与交互 ---
const PROMPT_KEY = 'geminiPrompt';
const TITLE_KEY = 'videoTitle';
const ORIGINAL_TITLE_KEY = 'geminiOriginalVideoTitle';
const TIMESTAMP_KEY = 'timestamp';
const ACTION_TYPE_KEY = 'geminiActionType';
const VIDEO_TOTAL_DURATION_KEY = 'geminiVideoTotalDuration';
const FIRST_SEGMENT_END_TIME_KEY = 'geminiFirstSegmentEndTime';
const SUMMARY_BUTTON_ID = 'gemini-summarize-btn';
const SUBTITLE_BUTTON_ID = 'gemini-subtitle-btn';
const THUMBNAIL_BUTTON_CLASS = 'gemini-thumbnail-btn';
const THUMBNAIL_PROCESSED_FLAG = 'data-gemini-processed';
const YOUTUBE_NOTIFICATION_ID = 'gemini-yt-notification';
const YOUTUBE_CONFIRMATION_ID = 'gemini-yt-confirmation';
// 恢复原始通知样式
const YOUTUBE_NOTIFICATION_STYLE = {
position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)',
backgroundColor: 'rgba(0,0,0,0.85)', color: 'white', padding: '15px 35px 15px 20px',
borderRadius: '8px', zIndex: '99999', maxWidth: 'calc(100% - 40px)', textAlign: 'left',
boxSizing: 'border-box', whiteSpace: 'pre-wrap',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)'
};
const YOUTUBE_CONFIRMATION_STYLE = {
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
backgroundColor: 'rgba(33, 33, 33, 0.95)', color: 'white', padding: '20px 25px',
borderRadius: '12px', zIndex: '999999', maxWidth: 'calc(100% - 60px)', minWidth: '300px',
boxSizing: 'border-box', boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
display: 'flex', flexDirection: 'column', gap: '15px'
};
// 缩略图按钮样式
GM_addStyle(`
.${THUMBNAIL_BUTTON_CLASS} {
position: absolute;
top: 5px;
right: 5px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
z-index: 120;
display: flex;
align-items: center;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: auto !important;
}
#dismissible:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-grid-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-rich-item-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-compact-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-playlist-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-reel-item-renderer:hover .${THUMBNAIL_BUTTON_CLASS},
ytd-search ytd-video-renderer:hover .${THUMBNAIL_BUTTON_CLASS} {
opacity: 1 !important;
visibility: visible !important;
pointer-events: auto !important;
}
.${THUMBNAIL_BUTTON_CLASS}:hover {
background-color: rgba(0, 0, 0, 0.9);
opacity: 1 !important;
visibility: visible !important;
}
.gemini-confirmation-btn {
padding: 8px 20px;
border-radius: 4px;
border: none;
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color 0.2s ease;
}
.gemini-confirmation-confirm {
background-color: #1a73e8;
color: white;
}
.gemini-confirmation-confirm:hover {
background-color: #0d65d9;
}
.gemini-confirmation-cancel {
background-color: #5f6368;
color: white;
margin-right: 10px;
}
.gemini-confirmation-cancel:hover {
background-color: #494c50;
}
`);
// 辅助函数
function showNotification(elementId, message, styles, duration = 15000) {
let existing = document.getElementById(elementId);
if (existing) {
clearTimeout(parseInt(existing.dataset.timeoutId));
existing.remove();
}
const notif = document.createElement('div');
notif.id = elementId;
notif.textContent = message;
Object.assign(notif.style, styles);
document.body.appendChild(notif);
const btn = document.createElement('button');
btn.textContent = '✕';
Object.assign(btn.style, { position: 'absolute', top: '5px', right: '10px', background: 'transparent', border: 'none', color: 'inherit', fontSize: '16px', cursor: 'pointer', padding: '0', lineHeight: '1' });
btn.onclick = () => notif.remove();
notif.appendChild(btn);
notif.dataset.timeoutId = setTimeout(() => notif.remove(), duration).toString();
return notif;
}
function showConfirmation(elementId, title, message, videoInfo, onConfirm, onCancel, styles) {
let existing = document.getElementById(elementId);
if (existing) existing.remove();
const dialog = document.createElement('div');
dialog.id = elementId;
Object.assign(dialog.style, styles);
document.body.appendChild(dialog);
const titleElem = document.createElement('h3');
titleElem.textContent = title;
titleElem.style.margin = '0 0 10px 0';
titleElem.style.fontSize = '18px';
const messageElem = document.createElement('div');
messageElem.textContent = message;
messageElem.style.marginBottom = '15px';
messageElem.style.fontSize = '14px';
const videoTitleElem = document.createElement('div');
videoTitleElem.textContent = `视频标题: ${videoInfo.title}`;
videoTitleElem.style.marginBottom = '5px';
videoTitleElem.style.fontWeight = 'bold';
const videoIdElem = document.createElement('div');
videoIdElem.textContent = `视频ID: ${videoInfo.id}`;
videoIdElem.style.fontSize = '12px';
videoIdElem.style.color = '#aaa';
videoIdElem.style.marginBottom = '15px';
const buttonsContainer = document.createElement('div');
buttonsContainer.style.display = 'flex';
buttonsContainer.style.justifyContent = 'flex-end';
buttonsContainer.style.gap = '10px';
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.className = 'gemini-confirmation-btn gemini-confirmation-cancel';
cancelBtn.onclick = () => {
dialog.remove();
if (onCancel) onCancel();
};
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确认';
confirmBtn.className = 'gemini-confirmation-btn gemini-confirmation-confirm';
confirmBtn.onclick = () => {
dialog.remove();
if (onConfirm) onConfirm(videoInfo);
};
buttonsContainer.appendChild(cancelBtn);
buttonsContainer.appendChild(confirmBtn);
dialog.appendChild(titleElem);
dialog.appendChild(messageElem);
dialog.appendChild(videoTitleElem);
dialog.appendChild(videoIdElem);
dialog.appendChild(buttonsContainer);
return dialog;
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).catch(() => {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
try { document.execCommand('copy'); } catch {}
document.body.removeChild(ta);
});
}
function isVideoPage() {
return window.location.pathname === '/watch' && new URLSearchParams(window.location.search).has('v');
}
// 验证YouTube视频ID格式
function isValidYouTubeVideoId(id) {
// YouTube视频ID通常是11位字符,由字母、数字、下划线和连字符组成
return id && typeof id === 'string' && /^[A-Za-z0-9_-]{11}$/.test(id);
} function getVideoInfoFromElement(element) {
if (element.hasAttribute(THUMBNAIL_PROCESSED_FLAG)) return null;
let videoId = '';
let videoTitle = '';
// 优先从数据属性中提取视频ID
if (element.dataset && element.dataset.videoId) {
videoId = element.dataset.videoId;
} else if (element.getAttribute('video-id')) {
videoId = element.getAttribute('video-id');
}
// 尝试从各种可能的属性和元素中提取视频ID
if (!isValidYouTubeVideoId(videoId)) {
// 1. 首先从链接中查找
const linkElements = Array.from(element.querySelectorAll('a[href*="/watch?v="]'));
for (const link of linkElements) {
const match = link.href.match(/\/watch\?v=([^&]+)/);
if (match && match[1] && isValidYouTubeVideoId(match[1])) {
videoId = match[1];
break;
}
}
// 2. 从缩略图元素寻找
if (!isValidYouTubeVideoId(videoId)) {
const thumbnailElements = element.querySelectorAll('img[src*="/vi/"], img[src*="i.ytimg.com"]');
for (const img of thumbnailElements) {
const match = img.src.match(/\/vi\/([^\/]+)\//) || img.src.match(/\/([A-Za-z0-9_-]{11})\/[\w]+\.jpg/);
if (match && match[1] && isValidYouTubeVideoId(match[1])) {
videoId = match[1];
break;
}
}
}
// 3. 从缩略图容器data属性获取
if (!isValidYouTubeVideoId(videoId)) {
const thumbnails = element.querySelectorAll('#thumbnail, .thumbnail, ytd-thumbnail');
for (const thumb of thumbnails) {
if (thumb.dataset && thumb.dataset.videoId && isValidYouTubeVideoId(thumb.dataset.videoId)) {
videoId = thumb.dataset.videoId;
break;
}
// 来自href属性
if (thumb.tagName === 'A' && thumb.href) {
const match = thumb.href.match(/\/watch\?v=([^&]+)/);
if (match && match[1] && isValidYouTubeVideoId(match[1])) {
videoId = match[1];
break;
}
}
}
}
// 4. 从视频渲染器元素获取
if (!isValidYouTubeVideoId(videoId)) {
const renderers = element.closest('ytd-rich-item-renderer, ytd-grid-video-renderer, ytd-video-renderer, ytd-compact-video-renderer');
if (renderers && renderers.dataset && renderers.dataset.videoId && isValidYouTubeVideoId(renderers.dataset.videoId)) {
videoId = renderers.dataset.videoId;
}
}
}
// 增强标题提取方法
const titleSelectors = [
'#video-title',
'.title',
'[title]',
'h3 a',
'h3',
'a[title]',
'span[title]',
'yt-formatted-string',
'[aria-label]'
];
for (const selector of titleSelectors) {
const titleElements = element.querySelectorAll(selector);
for (const titleElement of titleElements) {
const possibleTitle = titleElement.textContent?.trim() ||
titleElement.getAttribute('title')?.trim() ||
titleElement.getAttribute('aria-label')?.trim();
if (possibleTitle && possibleTitle.length > 5) {
videoTitle = possibleTitle;
break;
}
}
if (videoTitle) break;
}
// 最后验证结果
if (!isValidYouTubeVideoId(videoId) || !videoTitle) {
return null;
}
return {
id: videoId,
title: videoTitle,
url: `https://www.youtube.com/watch?v=${videoId}`
};
}
function processVideoSummary(videoInfo) {
const prompt = `请分析这个YouTube视频: ${videoInfo.url}\n\n提供一个全面的摘要,包括主要观点、关键见解和视频中讨论的重要细节,以结构化的方式分解内容,并包括任何重要的结论或要点。`;
GM_setValue(PROMPT_KEY, prompt);
GM_setValue(TITLE_KEY, videoInfo.title);
GM_setValue(ORIGINAL_TITLE_KEY, videoInfo.title);
GM_setValue(TIMESTAMP_KEY, Date.now());
GM_setValue(ACTION_TYPE_KEY, 'summary');
window.open('https://gemini.google.com/', '_blank');
showNotification(
YOUTUBE_NOTIFICATION_ID,
`已跳转到 Gemini!\n系统将尝试自动输入提示词并发送请求。\n\n视频: "${videoInfo.title}"\n\n(如果自动操作失败,提示词已复制到剪贴板,请手动粘贴)`,
YOUTUBE_NOTIFICATION_STYLE,
10000
);
copyToClipboard(prompt);
}
function handleThumbnailButtonClick(event, videoInfo) {
if (event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
// 确保不会触发原始点击事件
if (event.cancelable) event.returnValue = false;
}
if (!videoInfo || !videoInfo.url || !videoInfo.title) {
showNotification(
YOUTUBE_NOTIFICATION_ID,
"无法获取视频信息,请尝试直接在视频页面使用总结功能。",
{ ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' },
5000
);
return false;
}
// 验证视频ID
if (!isValidYouTubeVideoId(videoInfo.id)) {
showNotification(
YOUTUBE_NOTIFICATION_ID,
`获取到的视频ID格式无效: ${videoInfo.id}\n请尝试直接在视频页面使用总结功能。`,
{ ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' },
5000
);
return false;
}
// 显示确认对话框 - 保留缩略图总结的二次确认
showConfirmation(
YOUTUBE_CONFIRMATION_ID,
"确认视频信息",
"请确认以下视频信息是否正确:",
videoInfo,
processVideoSummary,
null,
YOUTUBE_CONFIRMATION_STYLE
);
return false;
}
function addThumbnailButtons() {
if (isVideoPage()) return;
const isSearchPage = window.location.pathname === '/results';
const videoElementSelectors = [
'ytd-rich-item-renderer',
'ytd-grid-video-renderer',
'ytd-video-renderer',
'ytd-compact-video-renderer',
'ytd-playlist-video-renderer',
'ytd-reel-item-renderer',
'.ytd-video-preview',
'.video-card',
'.ytd-compact-playlist-renderer',
'ytd-grid-playlist-renderer'
];
if (isSearchPage) videoElementSelectors.push('ytd-search ytd-video-renderer');
document.querySelectorAll(videoElementSelectors.join(',')).forEach(element => {
if (element.hasAttribute(THUMBNAIL_PROCESSED_FLAG) || element.querySelector(`.${THUMBNAIL_BUTTON_CLASS}`)) {
element.setAttribute(THUMBNAIL_PROCESSED_FLAG, 'true');
return;
}
// 增强缩略图容器选择
let thumbnailContainer = isSearchPage ?
(element.querySelector('ytd-thumbnail') || element.querySelector('a#thumbnail') || element.querySelector('[id="thumbnail"]')) :
element.querySelector('#thumbnail, .thumbnail, a[href*="/watch"], ytd-thumbnail');
if (!thumbnailContainer) return;
const videoInfo = getVideoInfoFromElement(element);
if (!videoInfo) return;
const button = document.createElement('button');
button.className = THUMBNAIL_BUTTON_CLASS;
button.textContent = '📝 总结';
button.title = '使用Gemini总结此视频';
// 增强事件处理
const eventHandler = (e) => {
if (e.type === 'click') {
return handleThumbnailButtonClick(e, videoInfo);
} else {
e.stopPropagation();
e.preventDefault();
if (e.cancelable) e.returnValue = false;
return false;
}
};
['click', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach(type => {
button.addEventListener(type, eventHandler, { capture: true, passive: false });
});
if (getComputedStyle(thumbnailContainer).position === 'static') {
thumbnailContainer.style.position = 'relative';
}
if (isSearchPage) {
Object.assign(button.style, {
zIndex: '999',
pointerEvents: 'auto',
position: 'absolute',
top: '5px',
right: '5px'
});
}
thumbnailContainer.appendChild(button);
element.setAttribute(THUMBNAIL_PROCESSED_FLAG, 'true');
});
}
function setupThumbnailButtonSystem() {
addThumbnailButtons();
const obs = new MutationObserver(() => setTimeout(addThumbnailButtons, 300));
obs.observe(document.body, { childList: true, subtree: true });
setInterval(() => {
if (!isVideoPage()) addThumbnailButtons();
}, 1500);
window.addEventListener('load', () => setTimeout(addThumbnailButtons, 1000));
}
function addYouTubeActionButtons() {
if (!isVideoPage()) {
removeYouTubeActionButtonsIfExists();
return;
}
if (document.getElementById(SUMMARY_BUTTON_ID) || document.getElementById(SUBTITLE_BUTTON_ID)) return;
// 寻找顶部导航区域 - 搜索框右侧的区域
const container = document.querySelector('#end') ||
document.querySelector('ytd-masthead #container') ||
document.querySelector('ytd-masthead');
if (!container) return;
// 创建一个容器包装我们的按钮
const buttonsWrapper = document.createElement('div');
buttonsWrapper.style.display = 'inline-flex';
buttonsWrapper.style.alignItems = 'center';
buttonsWrapper.style.marginRight = '16px';
const subtitleButton = document.createElement('button');
subtitleButton.id = SUBTITLE_BUTTON_ID;
subtitleButton.textContent = '🎯 生成字幕';
Object.assign(subtitleButton.style, {
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '18px',
padding: '0 16px',
margin: '0 8px 0 0',
cursor: 'pointer',
fontWeight: '500',
height: '36px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '14px',
zIndex: '100',
whiteSpace: 'nowrap',
transition: 'all 0.2s ease'
});
const summaryButton = document.createElement('button');
summaryButton.id = SUMMARY_BUTTON_ID;
summaryButton.textContent = '📝 Gemini摘要';
Object.assign(summaryButton.style, {
backgroundColor: '#1a73e8',
color: 'white',
border: 'none',
borderRadius: '18px',
padding: '0 16px',
margin: '0',
cursor: 'pointer',
fontWeight: '500',
height: '36px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '14px',
zIndex: '100',
whiteSpace: 'nowrap',
transition: 'all 0.2s ease'
});
// 为移动设备添加响应式适应
const mediaQuery = window.matchMedia('(max-width: 768px)');
const adjustForMobile = () => {
if (mediaQuery.matches) {
subtitleButton.style.fontSize = '12px';
subtitleButton.style.padding = '0 10px';
subtitleButton.style.height = '32px';
summaryButton.style.fontSize = '12px';
summaryButton.style.padding = '0 10px';
summaryButton.style.height = '32px';
} else {
subtitleButton.style.fontSize = '14px';
subtitleButton.style.padding = '0 16px';
subtitleButton.style.height = '36px';
summaryButton.style.fontSize = '14px';
summaryButton.style.padding = '0 16px';
summaryButton.style.height = '36px';
}
};
mediaQuery.addEventListener('change', adjustForMobile);
adjustForMobile();
subtitleButton.addEventListener('click', handleGenerateSubtitlesClick);
summaryButton.addEventListener('click', handleSummarizeClick);
buttonsWrapper.appendChild(subtitleButton);
buttonsWrapper.appendChild(summaryButton);
// 将按钮添加到顶部导航区域
// 在创建按钮之前插入
const createButton = container.querySelector('#create-icon') || container.querySelector('button[aria-label*="创建"]');
if (createButton) {
container.insertBefore(buttonsWrapper, createButton);
} else {
// 如果找不到创建按钮,就插入到容器前面
container.insertBefore(buttonsWrapper, container.firstChild);
}
}
// 视频页面摘要函数 - 移除确认步骤,直接处理
function handleSummarizeClick() {
const youtubeUrl = window.location.href;
// 从URL获取视频ID并验证
const urlParams = new URLSearchParams(window.location.search);
const videoId = urlParams.get('v');
if (!isValidYouTubeVideoId(videoId)) {
showNotification(
YOUTUBE_NOTIFICATION_ID,
"无法获取有效的视频ID,请确认当前是否在YouTube视频页面。",
{ ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' },
5000
);
return;
}
// 增强标题选择
const titleSelectors = [
'h1.ytd-watch-metadata',
'#video-title',
'#title h1',
'.title',
'yt-formatted-string.ytd-watch-metadata'
];
let videoTitle = '';
for (const selector of titleSelectors) {
const titleElement = document.querySelector(selector);
if (titleElement) {
videoTitle = titleElement.textContent?.trim();
if (videoTitle) break;
}
}
if (!videoTitle) {
videoTitle = document.title.replace(/ - YouTube$/, '').trim() || 'Unknown Video';
}
const videoInfo = {
id: videoId,
title: videoTitle,
url: youtubeUrl
};
// 直接处理,不显示确认对话框
processVideoSummary(videoInfo);
}
// 完全保留原始字幕生成函数
function handleGenerateSubtitlesClick() {
const youtubeUrl = window.location.href;
const titleElement = document.querySelector('h1.ytd-watch-metadata, #video-title, #title h1, .title');
const videoTitle = titleElement?.textContent?.trim() || document.title.replace(/ - YouTube$/, '').trim() || 'Unknown Video';
let videoDurationInSeconds = 0;
const durationMeta = document.querySelector('meta[itemprop="duration"]');
if (durationMeta?.content) {
const match = durationMeta.content.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
if (match) {
videoDurationInSeconds = 0;
if (match[1]) videoDurationInSeconds += parseInt(match[1].replace('H', '')) * 3600;
if (match[2]) videoDurationInSeconds += parseInt(match[2].replace('M', '')) * 60;
if (match[3]) videoDurationInSeconds += parseInt(match[3].replace('S', ''));
}
}
if (videoDurationInSeconds <= 0) {
showNotification(YOUTUBE_NOTIFICATION_ID, "无法获取视频时长,无法启动字幕任务。", { ...YOUTUBE_NOTIFICATION_STYLE, backgroundColor: '#d93025' }, 15000); return;
}
const firstSegmentEnd = Math.min(videoDurationInSeconds, 1200);
const prompt = `${youtubeUrl}\n1.不要添加自己的语言\n2.变成简体中文,流畅版本。\n\nYouTube\n请提取此视频从00:00:00到${new Date(firstSegmentEnd * 1000).toISOString().substr(11, 8)}的完整字幕文本。`;
GM_setValue(PROMPT_KEY, prompt);
GM_setValue(TITLE_KEY, `${videoTitle} (字幕 00:00:00-${new Date(firstSegmentEnd * 1000).toISOString().substr(11, 8)})`);
GM_setValue(ORIGINAL_TITLE_KEY, videoTitle);
GM_setValue(TIMESTAMP_KEY, Date.now());
GM_setValue(ACTION_TYPE_KEY, 'subtitle');
GM_setValue(VIDEO_TOTAL_DURATION_KEY, videoDurationInSeconds);
GM_setValue(FIRST_SEGMENT_END_TIME_KEY, firstSegmentEnd);
showNotification(YOUTUBE_NOTIFICATION_ID, `已跳转到 Gemini 生成字幕: 00:00:00 - ${new Date(firstSegmentEnd * 1000).toISOString().substr(11, 8)}...\n"${videoTitle}"`, YOUTUBE_NOTIFICATION_STYLE, 15000);
window.open('https://gemini.google.com/', '_blank');
copyToClipboard(prompt);
}
function removeYouTubeActionButtonsIfExists() {
[SUMMARY_BUTTON_ID, SUBTITLE_BUTTON_ID].forEach(id => {
const button = document.getElementById(id);
if (button) button.remove();
});
}
// --- 页面类型检测函数 ---
function detectYouTubePageType() {
// 确保body元素存在
if (!document.body) return;
// 确定页面类型
let isHomePage = window.location.pathname === '/' || window.location.pathname === '/feed/subscriptions';
let isChannelPage = window.location.pathname.includes('/channel/') ||
window.location.pathname.includes('/c/') ||
window.location.pathname.includes('/user/') ||
window.location.pathname.includes('/@');
let isSearchPage = window.location.pathname === '/results';
// 添加页面类型属性
if (isHomePage) {
document.body.setAttribute('data-is-home-page', 'true');
document.body.setAttribute('data-page-subtype', 'home');
} else {
document.body.removeAttribute('data-is-home-page');
}
if (isChannelPage) {
document.body.setAttribute('data-page-subtype', 'channels');
} else if (isSearchPage) {
document.body.setAttribute('data-page-type', 'search');
}
}
// --- 页面初始化 ---
if (window.location.hostname.includes('www.youtube.com')) {
// 检测页面类型并设置相应的属性
detectYouTubePageType();
// 当URL变化时重新检测页面类型
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(() => {
detectYouTubePageType();
if (isVideoPage()) {
addYouTubeActionButtons();
} else {
removeYouTubeActionButtonsIfExists();
}
}, 1000);
}
});
urlObserver.observe(document, { subtree: true, childList: true });
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setupThumbnailButtonSystem();
setTimeout(addYouTubeActionButtons, 1000);
// 每隔一段时间检查视频页面按钮
setInterval(() => {
if (isVideoPage()) {
if (!document.getElementById(SUMMARY_BUTTON_ID) || !document.getElementById(SUBTITLE_BUTTON_ID)) {
addYouTubeActionButtons();
}
}
// 重新检测页面类型
detectYouTubePageType();
}, 5000);
} else {
document.addEventListener('DOMContentLoaded', () => {
detectYouTubePageType();
setupThumbnailButtonSystem();
setTimeout(addYouTubeActionButtons, 1000);
}, { once: true });
}
} else if (window.location.hostname.includes('gemini.google.com')) {
// Gemini自动填充 - 简化版,删除了所有自动分段处理
const prompt = GM_getValue(PROMPT_KEY);
const timestamp = GM_getValue(TIMESTAMP_KEY, 0);
const actionType = GM_getValue(ACTION_TYPE_KEY);
// 检查是否有有效的待处理请求且来源是YouTube
const referrerIsYouTube = document.referrer.includes('youtube.com');
if (prompt && actionType && Date.now() - timestamp <= 300000 && referrerIsYouTube) {
setTimeout(() => {
const textarea = document.querySelector('textarea, div[contenteditable="true"]');
if (textarea) {
if (textarea.isContentEditable) textarea.textContent = prompt;
else textarea.value = prompt;
textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
setTimeout(() => {
const sendBtn = document.querySelector('button[aria-label*="Send"],button[aria-label*="发送"],button[aria-label*="提交"],button[aria-label*="Run"],button[aria-label*="Submit"]');
if (sendBtn && !sendBtn.disabled) {
sendBtn.click();
// 单次操作完成后清理数据
setTimeout(() => {
GM_deleteValue(PROMPT_KEY);
GM_deleteValue(TITLE_KEY);
GM_deleteValue(ORIGINAL_TITLE_KEY);
GM_deleteValue(TIMESTAMP_KEY);
GM_deleteValue(ACTION_TYPE_KEY);
GM_deleteValue(VIDEO_TOTAL_DURATION_KEY);
GM_deleteValue(FIRST_SEGMENT_END_TIME_KEY);
}, 5000); // 延迟清理以确保提交后处理完成
}
}, 500);
}
}, 1200);
}
}
})();