您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Summarize webpage, selected text or YouTube transcript via local API
当前为
// ==UserScript== // @name GPT4Free Page Summarizer // @version 1.7 // @description Summarize webpage, selected text or YouTube transcript via local API // @author SH3LL // @match *://*/* // @grant GM.xmlHttpRequest // @run-at document-end // @namespace http://tampermonkey.net/ // ==/UserScript== (function () { 'use strict'; // === Globals === let loading = false; let ytTranscript = null; let isSidebarVisible = false; let isTextSelected = false; let hoverTimeout; const isYouTubeVideoPage = window.location.hostname.includes("youtube.com") && window.location.pathname === "/watch"; const browserLanguage = navigator.language; const selectedLanguage = getDisplayLanguage(browserLanguage); // === Init DOM UI === const { shadowRoot, sidebar, toggleButton, summarizeButton, statusDisplay, summaryContainer } = createSidebarUI(); document.body.appendChild(shadowRoot.host); setTimeout(() => { toggleButton.style.opacity = '0.3'; }, 2000); // === Start transcript load if on YouTube === if (isYouTubeVideoPage) { loadYouTubeTranscript().then(transcript => { ytTranscript = transcript; console.log("YOUTUBE Transcript:", transcript); updateButtonText(); }).catch(console.error); } // === Add Event Listeners === document.addEventListener('mouseup', updateButtonText); document.addEventListener('mousedown', () => setTimeout(updateButtonText, 100)); toggleButton.addEventListener('click', toggleSidebar); toggleButton.addEventListener('mouseover', () => { clearTimeout(hoverTimeout); toggleButton.style.opacity = '1'; }); toggleButton.addEventListener('mouseout', () => { hoverTimeout = setTimeout(() => { if (!isSidebarVisible) toggleButton.style.opacity = '0.3'; }, 3000); }); summarizeButton.addEventListener('click', handleSummarizeClick); // === Initial button text === updateButtonText(); updateStatusDisplay('Idle', '#888888'); // === Language Utility === function getDisplayLanguage(langCode) { try { const name = new Intl.DisplayNames([langCode], { type: 'language' }).of(langCode); return name || langCode; } catch { return langCode; } } // === Summarize Request === function summarizePage(text, lang) { return new Promise((resolve, reject) => { const prompt = `Summarize the following text in ${lang}. The summary is organised in blocks of topics. Return the result in a json list composed of dictionaries with fields "title" (the title starts with a contextual emoji) and "text". Don't add any other sentence like "Here is the summary", or coding formatting like \"\`\`\`json\", write directly the summary itself. Exclude from the summary any advertisement or sponsorization. Here is the text: ${text}`; const payload = { messages: [{ role: 'user', content: prompt }], model: 'uncensored-r1',//'DeepSeek-R1', provider: 'TypeGPT'//'Blackbox' }; GM.xmlHttpRequest({ method: 'POST', url: 'http://localhost:1337/v1/chat/completions', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(payload), onload: response => { const color = (response.status >= 200 && response.status < 300) ? '#00ff00' : '#ffcc00'; resolve({ status: response.status, responseText: response.responseText, color }); }, onerror: err => reject({ message: 'Network error', color: '#ff4444' }) }); }); } // === YouTube Transcript Extraction === function loadYouTubeTranscript() { return new Promise((resolve, reject) => { const TRANSCRIPT_ID = "engagement-panel-searchable-transcript"; const SELECTOR = `ytd-engagement-panel-section-list-renderer[target-id="${TRANSCRIPT_ID}"] #content`; const panelObserver = new MutationObserver((mutationsList, observerInstance) => { const panel = document.querySelector(`ytd-engagement-panel-section-list-renderer[target-id="${TRANSCRIPT_ID}"]`); if (panel) { observerInstance.disconnect(); panel.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_EXPANDED"); const content = panel.querySelector('#content'); if (content) { observeTranscriptContent(content, panel); } else { reject("Contenuto non trovato nel pannello."); } } }); panelObserver.observe(document.body, { childList: true, subtree: true }); function observeTranscriptContent(content, panel) { const contentObserver = new MutationObserver((mutationsList, observerInstance) => { if (content.children.length > 0) { const transcript = Array.from(content.querySelectorAll('.segment-text')) .map(seg => seg.textContent.trim()) .join(" "); resolve(transcript.trim()); observerInstance.disconnect(); panel.setAttribute("visibility", "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"); } }); contentObserver.observe(content, { childList: true, subtree: true }); setTimeout(() => { contentObserver.disconnect(); reject("Timeout: Trascrizione non trovata."); }, 10000); } setTimeout(() => { panelObserver.disconnect(); reject("Timeout: Pannello di trascrizione non trovato."); }, 10000); }); } // === Sidebar Toggle === function toggleSidebar() { isSidebarVisible = !isSidebarVisible; sidebar.style.right = isSidebarVisible ? '0' : '-300px'; toggleButton.style.right = isSidebarVisible ? '300px' : '0'; setTimeout(() => { toggleButton.style.opacity = isSidebarVisible ? '1' : '0.3'; }, 1000); } // === UI Updates === function updateButtonText() { if (loading) { summarizeButton.textContent = 'Loading..'; return; } const selectedText = window.getSelection().toString(); if (selectedText) { summarizeButton.textContent = `Summary [${selectedText.substring(0, 2).trim()}..]`; isTextSelected = true; } else if (isYouTubeVideoPage) { summarizeButton.textContent = 'Summary 📹️'; isTextSelected = false; } else { summarizeButton.textContent = 'Summary'; isTextSelected = false; } } function updateStatusDisplay(text, color) { while (statusDisplay.firstChild) { statusDisplay.removeChild(statusDisplay.firstChild); } const statusLabel = document.createElement('span'); statusLabel.textContent = 'Status: '; const statusText = document.createElement('span'); statusText.textContent = text; statusText.style.color = color; const langLabel = document.createElement('span'); langLabel.textContent = ' | Lang: '; const langText = document.createElement('span'); langText.textContent = selectedLanguage; langText.style.color = '#00bfff'; statusDisplay.appendChild(statusLabel); statusDisplay.appendChild(statusText); statusDisplay.appendChild(langLabel); statusDisplay.appendChild(langText); } function craftJson(jsonData) { const container = document.createElement('div'); JSON.parse(jsonData).forEach(block => { const title = document.createElement('strong'); title.textContent = block.title; const text = document.createElement('div'); text.textContent = block.text; container.appendChild(title); container.appendChild(text); container.appendChild(document.createElement('br')); }); return container; } function handleSummarizeClick() { if (loading) return; updateButtonText(); updateStatusDisplay('Requesting..', '#888888'); summaryContainer.style.display = 'none'; let content = ''; if (isYouTubeVideoPage && ytTranscript && isTextSelected!=true) { content = ytTranscript; } else if (isTextSelected) { content = window.getSelection().toString(); } else { content = document.body.innerText; } loading = true; summarizeButton.disabled = true; summarizePage(content, selectedLanguage) .then(({ status, responseText, color }) => { try { const json = JSON.parse(responseText); const result = json.choices?.[0]?.message?.content || 'Invalid API response format.'; summaryContainer.append(craftJson(result)); summaryContainer.style.color = '#ffffff'; updateStatusDisplay(`Success (${status})`, color); } catch (err) { summaryContainer.textContent = `Error parsing response: ${err}`; summaryContainer.style.color = '#ff4444'; updateStatusDisplay(`Failed (${status})`, '#ff4444'); } }) .catch(err => { summaryContainer.textContent = `Error: ${err.message}`; summaryContainer.style.color = '#ff4444'; updateStatusDisplay('Failed', err.color); }) .finally(() => { loading = false; summarizeButton.disabled = false; updateButtonText(); summaryContainer.style.display = 'block'; }); } // === UI Construction === function createSidebarUI() { const host = document.createElement('div'); const root = host.attachShadow({ mode: 'open' }); const sidebar = document.createElement('div'); Object.assign(sidebar.style, { position: 'fixed', right: '-300px', top: '0', width: '300px', height: '100vh', backgroundColor: '#000', color: '#fff', padding: '20px', zIndex: '999999', fontFamily: 'Arial, sans-serif', display: 'flex', flexDirection: 'column', gap: '10px', boxSizing: 'border-box', transition: 'right 0.3s ease', borderLeft: '1px solid #cccccc', borderTopLeftRadius: '5px', borderBottomLeftRadius: '5px' }); const toggleBtn = document.createElement('button'); Object.assign(toggleBtn.style, { position: 'fixed', right: '0', top: '20px', backgroundColor: '#333', color: '#000', border: '1px solid #cccccc', borderRadius: '6px', padding: '10px', cursor: 'pointer', zIndex: '1000000', fontSize: '14px', transition: 'right 0.3s ease, opacity 0.3s ease' }); toggleBtn.textContent = '✨'; const container = document.createElement('div'); Object.assign(container.style, { display: 'flex', gap: '10px', alignItems: 'center' }); const summarizeBtn = document.createElement('button'); Object.assign(summarizeBtn.style, { backgroundColor: '#333', color: '#fff', border: '1px solid #cccccc', borderRadius: '6px', padding: '10px 20px', cursor: 'pointer', fontSize: '14px', flex: '1', transition: 'background-color 0.3s' }); summarizeBtn.onmouseover = () => summarizeBtn.style.backgroundColor = '#4d4d4d'; summarizeBtn.onmouseout = () => summarizeBtn.style.backgroundColor = '#333'; const status = document.createElement('div'); status.style.fontSize = '12px'; const summary = document.createElement('div'); Object.assign(summary.style, { fontSize: '14px', lineHeight: '1.5', display: 'none', overflowY: 'auto', maxHeight: 'calc(100vh - 130px)', whiteSpace: 'pre-line' }); container.appendChild(summarizeBtn); sidebar.appendChild(container); sidebar.appendChild(status); sidebar.appendChild(summary); root.appendChild(sidebar); root.appendChild(toggleBtn); return { shadowRoot: root, sidebar, toggleButton: toggleBtn, summarizeButton: summarizeBtn, statusDisplay: status, summaryContainer: summary }; } })();