您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fill the page link titles with the full respective page titles, if possible
// ==UserScript== // @name Retrieve Full Page Titles in Google Search // @description Fill the page link titles with the full respective page titles, if possible // @version 2.0.1 // @namespace Google // @author Benjamin Philipp <dev [at - please don't spam] benjamin-philipp.com> // @include /https?:\/\/(www\.)?google\.[a-z\.]{2,6}\/(search|webhp)\?((?!tbm=isch).)*$/ // @require http://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // @require https://gf.qytechs.cn/scripts/447081-bp-funcs/code/BP%20Funcs.js // @run-at document-body // @noframes // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @connect * // ==/UserScript== // 2.0.0 // - Using GM_Config for settings now (https://github.com/sizzlemctwizzle/GM_config) 🖤 // (Available from the Tampermonkey menu, under the script name) // - Added setting to disregard new titles if they're shorter than the old ones. Duh, should have done that from the start ^^ // (ON by default) // - The new titles, as well as additional info (old titles, errors, warnings) are now always shown in the element title (mouse hover tooltip), where applicable // - Moved away from extending the whole layout width in favor of just showing the overflowing new titles. // This is to prevent clashes with google widgets and panels that may appear on the right. // If anything is covered, the longet titles are now always available on mouse hover over the links. // - Fixed a potential issue where titles containing special chars could inadvertently render HTML instead of just their escaped text 😬 // - Using my function library again, also for nicer logging // - moved to classes for styling instead of "spaghetti" element styles // I dunno, probably some other stuff. I'm an artist, not a book keeper 😜 // 1.9.7: // added newer CSS variable "center-column-width" for unrestricting column width // 1.9.6: // Remove width limit for results column and items // 1.9.5: // corrected selectors for color changes // 1.9.4: // changed background colors from "very light shades" to "transparent clors", for dark mode var settings = {}; settings.applyToLinkText = true; settings.rex = "<title([^>]*)>(.*?)<\\/title>"; settings.dontLookupExtensions = "pdf"; settings.ignoreShorter = true; settings.ignoreTitles = "^Just a moment...\n^Continue$\n^Untitled$\n^Please wait$\n^Redirecting$\n^Watch$\n/^(log|sign )in$\n^Reddit - Dive into anything/i"; settings.verbosity = 1; var logger = new BPLogger(GM_info.script.name); if("undefined" != typeof GM_config){ GM_config.init( { 'id': 'MyConfig', 'title': GM_info.script.name + ' Settings', 'fields': { 'applyToLinkText': { 'label': '<b>Replace the actual <i>link text</i> with any found title</b><br /> <i>ON:</i> Change innerHTML of links and applying overflow: visible to parents <br /><i>OFF:</i> Only apply to Link Title (for mouseover Tooltip) <br /><i class="small">Default: ON</i> ', 'type': 'checkbox', 'default': true }, 'rex': { 'label': '<b>Regex to find the title of a page.</b> If you find a better way, please <a href="https://gf.qytechs.cn/en/scripts/27406-retrieve-full-page-titles-in-google-search/feedback" target="_blank">let me know</a>! <br /><i class="small">Default: "<title([^>]*)>(.*?)<\\/title>"</i>', 'type': 'text', 'default': "<title([^>]*)>(.*?)<\\/title>" }, 'dontLookupExtensions': { 'label': '<b>Exclude file extensions from lookup.</b> For example, links to .pdf file will usually trigger a downlaod when following the link with a GET request. <br /><i class="small">Separate with commas. Default: "pdf', 'type': 'text', 'default': "pdf" }, 'ignoreShorter': { 'label': '<b>Ignore new titles that are shorter than the old ones.</b> Titles shorter than the originally truncated ones are probably not what we want. <br /><i class="small">Default: ON</i>', 'type': 'checkbox', 'default': true }, 'ignoreTitles': { 'label': '<b>Ignore looked up titles when they return one of these.</b> Usually only needed when the above setting is OFF. <br /><i class="small">Can be RegEx by using /slash delimiters/. Default: <br />Just a moment...<br />Continue<br />Please wait<br />Redirecting<br />Watch<br />/^(log|sign )in$/i</i>', 'type': 'textarea', 'default': "Just a moment...\nContinue\nPlease wait\nRedirecting\nWatch\n/^(log|sign )in$/i" }, 'verbosity': { 'label': '<b>Console logging verbosity.</b> 0 = no logs; 1 = reports on link counts; 2 = +statuses of link checks; 3 = +Details <br /><i class="small">Default: 1</i>', 'type': 'int', 'default': 1 } }, 'css': ` #MyConfig{ background: #333; color: #ccc; line-height: 1.33em; } #MyConfig input[type="text"], #MyConfig input[type="email"], #MyConfig input[type="number"], #MyConfig input[type="password"], #MyConfig input[type="url"], #MyConfig input[type="submit"], #MyConfig input[type="reset"], #MyConfig input[type="button"], #MyConfig select, #MyConfig option, #MyConfig textarea { box-sizing: border-box; background: #1a1a1a; padding: 0.8em; color: #fff; vertical-align: bottom; } #MyConfig input[type="text"], #MyConfig input[type="email"], #MyConfig input[type="number"], #MyConfig input[type="password"], #MyConfig input[type="url"], #MyConfig textarea { width: 100%; border: 2px inset rgba(20,108,128,0.5); border-radius: 0.1em; } #MyConfig input:focus, #MyConfig textarea:focus { color: #fff; background: #000; } #MyConfig textarea { min-height: 6em; } #MyConfig .field_label{ font-weight: normal; font-size: 15px; } #MyConfig kbd{ border: 1px solid rgba(128,128,128,0.5); background: rgba(128,128,128,0.2); border-radius: 3px; padding: 1px 3px; font-family: consolas, monospace; } #MyConfig .code{ background: rgba(128,128,128,0.1); padding: 1px 3px; font-family: consolas, monospace; } #MyConfig .field_label .small{ font-size: 80%; } #MyConfig .config_header{ margin: 1em 0; } #MyConfig .config_var{ border: 1px solid rgba(128,128,128,0.5); padding: 1em; margin: 0.5em 0 0; border-radius: 0.5em; } #MyConfig a{ color: #fff; } #MyConfig .reset, #MyConfig .reset a, #MyConfig_buttons_holder { color: #aaa; } #MyConfig button, #MyConfig .button, #MyConfig input[type="submit"], #MyConfig input[type="reset"], #MyConfig input[type="button"], #MyConfig .saveclose_buttons{ background: rgba(40, 130,180,0.7); color: #ddd; border: 1px solid transparent; cursor: pointer; padding: 0.5em 1em; border-radius: 0.75em; transition: all 0.5s; } #MyConfig button:hover, #MyConfig .button:hover, #MyConfig input[type="submit"]:hover, #MyConfig input[type="reset"]:hover, #MyConfig input[type="button"]:hover{ background: rgba(0, 70,100,1); color: #fff; border-color: rgba(40, 210,255,1); border-radius: 0; } `, events: { init: main } }); } else{ log("Could not load GM_config! external resource may be temporarily down?\nUsing default settings for now.", 1, "error"); GM_registerMenuCommand(GM_info.script.name + ' Settings', function(){ alert("Could not load GM_config! external resource may be temporarily down?\nUsing default settings for now."); main(false); // TODO: nicer message // TODO: Manual lookup }); } logger.level = settings.verbosity; var myVersion = GM_info.script.version; var settingsSafety = settings; var linkmatch = "#search #rso .g a h3"; var resultsObserver; var idle = true; var idletimer; var updaterequest = false; var openRequests = 0; var successRequests = 0; var failedRequests = 0; var msgPrefix = "Full Page Titles in Google Search:\n"; var lastCount = null; function log(obj, level = 1, type = "default"){ if(!(obj instanceof Array)) obj = [obj]; logger.writeLog(obj, type, level); } log("Verbosity level: " + settings.verbosity, 1); GM_addStyle(` /* // Legacy, old version. #rso div.g{ width:auto!important; } .srp { --center-column: auto; --center-column-width: auto; --center-width: auto; } */ .showverflow{ overflow: visible!important; contain: layout!important; } .imgFix{ position: relative; top: 4em; } .titlesChecking{ background-color: rgba(255, 200, 0, 0.1); } .titlesFail{ background-color: rgba(255, 0, 0, 0.1); } .titlesOK{ background-color: rgba(30, 255, 0, 0.1); } `); var rexRex = /\/(.+)\/([dgimsuy]*)/; for(let i=0; i<settings.ignoreTitles.length; i++){ let x = settings.ignoreTitles[i]; let m = rexRex.exec(x); if(m && m.length>1){ try{ let rex = new RegExp(m[1], m[2]); settings.ignoreTitles[i] = rex; } catch(e){ log("Not a valid regular expression in settings.ignoreTitles: " + x, 1, "error"); } } } function updatePage(){ var allinks = 0; log("Walking through Links...", 2); $(linkmatch + ":not([titleDone], .titlesChecking)").each(function(){ allinks ++; // log(this, 3); // log("Looking at Link '" + $(this).parent()[0].href + "' (" + this.innerHTML + ")", 3); if(this.textContent.substr(this.textContent.length-3)=="..."){ log("'" + this.textContent + "' Needs checking", 2); $(this).addClass("titlesChecking"); getTitle(this); } }); if(lastCount !== openRequests) log(openRequests + " of " + allinks + " links need to be checked.", 1); lastCount = openRequests; } function getTitle(el){ var a = $(el).closest("a")[0]; log("Title '" + a.href + "' (" + el.textContent + ") looks shortened.", 3); for(var i=0; i<settings.dontLookupExtensions.length; i++) { if(a.href.endson(settings.dontLookupExtensions[i], true)){ log("Excluding Link '" + a.href + "' (" + el.textContent + ") because the extension (" + settings.dontLookupExtensions[i] + "), which is excluded", 3); el.title = "The extension " + settings.dontLookupExtensions[i] + " is excluded in settings"; $(el).removeClass("titlesChecking"); $(el).addClass("titlesFail"); $(el).attr("titleDone", "true"); return; } } openRequests++; $(el).addClass("titlesChecking"); GM_xmlhttpRequest({ url: a.href, method: "GET", timeout: 15000, //15 seconds timeout onload: function(res){ $(el).attr("titleDone", "true"); $(el).removeClass("titlesChecking"); var mrex = new RegExp(settings.rex, "i"); var tit = mrex.exec(res.response); if(tit === undefined || tit === null){ log("No title found in response for " + a.href, 2, "error"); el.title = "Failed to get title: No title found in response"; $(el).addClass("titlesFail"); report("fail"); return; } tit = unEscapeHtml(tit[2]); var oldTitle = $(el).text().trim(); if(settings.ignoreShorter && tit.length < oldTitle.length-3){ log("Ignoring title '" + tit + "' because it is shorter than the original (" + oldTitle + ")", 3, "warn"); el.title = "(Ignore:) " + tit; $(el).addClass("titlesFail"); report("success"); return; } for(let x of settings.ignoreTitles){ if((x instanceof RegExp && tit.match(x)) || (typeof x == "string" && x.toLowerCase() == tit.toLowerCase())){ log(["Ignoring title '" + tit + "' because of the excludion rule", x, "specified in settings"], 3, "warn"); el.title = "(Ignore:) " + tit; $(el).addClass("titlesFail"); report("success"); return; } } $(el).addClass("titlesOK"); if(settings.applyToLinkText){ $(el).text(tit); el.title = tit + "\n\nOriginal Title: " + oldTitle; $(a).css("white-space", "nowrap"); $(a).parentsUntil(".g").last().find("[data-content-feature] img").parents("[data-content-feature]").first().addClass("imgFix"); $(a).parentsUntil("#main", ":not(.showverflow)").addClass("showverflow"); } else el.title = tit; report("success"); }, onerror: function(res){ $(el).attr("titleDone", "true"); $(el).removeClass("titlesChecking"); $(el).addClass("titlesFail"); el.title = "Failed to get full title: Error loading page"; log({error: "Error loading page", link: el}, 2, "error"); report("fail"); }, ontimeout: function(res){ $(el).attr("titleDone", "true"); $(el).removeClass("titlesChecking"); $(el).addClass("titlesFail"); el.title = "Failed to get full title: Connection timed out"; log({error: "Connection timed out", link: el}, 2, "error"); report("fail"); } }); } function report(status){ switch(status){ case "success": successRequests ++; openRequests --; break; case "fail": failedRequests ++; openRequests --; break; } log(successRequests + " requests successful, " + failedRequests + " failed. " + openRequests + " Requests open.", 1); } function unEscapeHtml(text){ var t = document.createElement("TEXTAREA"); t.innerHTML = text; return t.value; } String.prototype.endson = function(str, insensitive){ return new RegExp("("+escapeRegExp(str)+")$", insensitive?"i":"").test(this); }; function escapeRegExp(str) { return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); } function updater(t = 1000){ if(idle) { idle = false; updaterequest = false; updatePage(); idletimer = setTimeout(function(){ idle = true; if(updaterequest) updatePage(); }, t); } else { log("Updater called but busy",3); updaterequest = true; } } function main(hasSettings=true){ if(hasSettings){ GM_registerMenuCommand(GM_info.script.name + ' Settings', function(){ GM_config.open(); }); settings.applyToLinkText = GM_config.get("applyToLinkText"); settings.rex = GM_config.get("rex"); settings.dontLookupExtensions = GM_config.get("dontLookupExtensions").split(",").map(a=>"." + a.trim()); settings.ignoreShorter = GM_config.get("ignoreShorter"); settings.ignoreTitles = GM_config.get("ignoreTitles").split(/\r?\n/); settings.verbosity = GM_config.get("verbosity"); } log("Start updater interval", 3); setInterval(updater, 2000); } /* jshint loopfunc: true, -W027 */ /* eslint-disable curly, no-redeclare */ /* eslint no-trailing-spaces: off */ /* globals $, GM_info, GM_setValue, GM_getValue, GM_xmlhttpRequest, GM_addStyle, GM_openInTab, GM_setClipboard, GM_config, escape, uneval, unsafeWindow, BPLogger_default, log, error, warn, getParam, waitFor, BPLogger */
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址