您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script modifies images to link to their original ("-og") high-res version. The link is available as soon as a border appears around an image. The color of the box indicates the range of the image height. In addition, the script unhides/expands all images of large multi-image posts and displays the timestamp of the post in the upper right corner (dashboard only).
当前为
// ==UserScript== // @name BDSMLR - clickable links to original high-res images and display timestamps // @namespace bdsmlr_linkify // @version 3.1.1 // @license GNU AGPLv3 // @description This script modifies images to link to their original ("-og") high-res version. The link is available as soon as a border appears around an image. The color of the box indicates the range of the image height. In addition, the script unhides/expands all images of large multi-image posts and displays the timestamp of the post in the upper right corner (dashboard only). // @author marp // @homepageURL https://gf.qytechs.cn/en/users/204542-marp // @include https://bdsmlr.com/ // @include https://bdsmlr.com/?group* // @include https://bdsmlr.com/dashboard* // @include https://bdsmlr.com/?latest* // @include https://*.bdsmlr.com/ // @include https://*.bdsmlr.com/post/* // @include https://bdsmlr.com/search/* // @include https://*.bdsmlr.com/search/* // @include https://bdsmlr.com/blog/* // @include https://bdsmlr.com/originalblogposts/* // @include https://bdsmlr.com/likes* // @include https://bdsmlr.com//* // @run-at document-end // ==/UserScript== // jshint esversion:8 //console.info("START href: ", window.location.href); //------------------------------------------------------------ // FIRST PART OF SCRIPT #2 - function that gets called by event oberver that is registered as part of 1st part #1 (see very end of this script) //------------------------------------------------------------ function createImageLinks(myDoc, myContext) { //console.info("createImageLinks: ", myContext); if (myDoc===null) myDoc = myContext; if (myDoc===null) return; if (myContext===null) myContext = myDoc; var tmpstr; var singlematch; var origpostlink; var origbloglink; var origblog; var matches, matches2; var imageurl; var imagesrc; var cdnmatches; var cdnnumber; // iterate over all posts within the supplied context // For BDSMLR, it does not seem to be necessary to also deal with the fact that the context could be WITHIN(!) a post (i.e. post is an ancestor) matches = myDoc.evaluate("./descendant-or-self::div[contains(@class,'postholder')] | ./descendant-or-self::div[contains(@class,'post_content')]", myContext, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0, el; (i<matches.snapshotLength); i++) { el = matches.snapshotItem(i); if (el) { try { // try to find info about original poster (if this is a reblog) as well as the link to the individual (potentially reblogged) post // both info only seem to be present on dashboard and on rightside overlay blogs - but not always on individual blogs (xxx.bdsmlr.com) or on individual blog post URLs :-( // This info is needed because in VERY early BDSMLR days the full resolution "-og" image sometimes only existed on the original blog hostname... // ...and these very old posts are still around... origblog = null; singlematch = myDoc.evaluate(".//div[contains(@class,'originalposter')]/a[contains(@href,'.bdsmlr.com/post/')]" + " | " + ".//div[contains(@class,'original')]/a[contains(@href,'.bdsmlr.com')]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); origpostlink = singlematch.singleNodeValue; // xxxx.bdsmlr.com/post/yyyyyyyy if (origpostlink) { tmpstr = origpostlink.getAttribute("href"); //everything after and including "/post" gets truncated away later anyway if ( tmpstr && (tmpstr.length > 10) && !(tmpstr.includes("//.bdsmlr.com") ) ) { // some urls are invalid and need to be ignored ("https://.bdsmlr.com/...") origblog = tmpstr; } } if (origblog === null) { //second method might find the originial blog URL (xxxx.bdsmlr.com) - however, often this is just a re-poster - not the original singlematch = myDoc.evaluate(".//div[contains(@class,'post_info')]//i[contains(@class,'retweet') or contains(@class,'rbthis')]" + "/following-sibling::a[contains(@class,'adata') or contains(@class,'ndata')]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); origbloglink = singlematch.singleNodeValue; // xxxx.bdsmlr.com if (origbloglink) { tmpstr = origbloglink.getAttribute("href"); if ( tmpstr && (tmpstr.length > 10) && !(tmpstr.includes("//.bdsmlr.com") ) ) { // some urls are invalid and need to be ignored ("https://.bdsmlr.com/...") origblog = tmpstr; } } if (origblog === null) { // if neither of the two above find anything then this is likely NOT a reblogged post but the original post -> get the orginial blog post URL singlematch = myDoc.evaluate(".//a[(contains(@class,'adata') or contains(@class,'ndata')) and contains(@href,'.bdsmlr.com/post/')]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); origpostlink = singlematch.singleNodeValue; // xxxx.bdsmlr.com if (origpostlink) { tmpstr = origpostlink.getAttribute("href"); if ( tmpstr && (tmpstr.length > 10) && !(tmpstr.includes("//.bdsmlr.com") ) ) { // some urls are invalid and need to be ignored ("https://.bdsmlr.com/...") origblog = tmpstr; } } if (origblog === null) { if ( !window.location.href.startsWith("https://bdsmlr.com") ) { // if no link to neither original blog nor original blog post was found then we assume that this is the original blog post or blog itself (this is a rather shaky assumtion - fingers crossed...) origblog = window.location.href; } } } } // if none of the above worked then we're out of luck and origblog remains null // iterate over all links to images (i.e. does NOT (yet) create links to images where none exist in the first place) // skip over items that already have a link to a "non-cdn" bdsmlr url or that are not bdsmlr links at all matches2 = myDoc.evaluate(".//div[contains(@class,'image_container') or contains(@class,'image_content')]" + "//a[(@href='') or ((contains(@class,'magnify') or contains(@class,'image-link')) and contains(@href,'https://cdn') and contains(@href,'.bdsmlr.com'))]/img" + " | " + ".//div[contains(@class,'image_container') or contains(@class,'image_content')]" + "//div[(@href='') or ((contains(@class,'magnify') or contains(@class,'image-link')) and contains(@href,'https://cdn') and contains(@href,'.bdsmlr.com'))]/img" + " | " + ".//div[contains(@class,'image_container') or contains(@class,'image_content')]//div[contains(@class,'textcontent')]" + "//img[contains(@src,'https://cdn') and contains(@src,'.bdsmlr.com')]" , el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var j=0, image, imageparent; (j<matches2.snapshotLength); j++) { image=matches2.snapshotItem(j); if (image) { imagesrc = image.src; imageparent = image.parentNode; imageurl = imageparent.getAttribute("href"); if (imageurl === null || imageurl.length < 5) { imageurl = image.getAttribute("src"); // No idea why this is needed... DevTools inspector always shows a valid image src attribute... but at script execution time... apparently not... seems to be some bdsmlr JavaScript post-processing... if (imageurl === null || imageurl.length < 5) { imageurl = image.getAttribute("data-echo"); } } if (imageurl && imageurl.length > 5) { getBestImageUrlPromise(imagesrc, imageurl, origblog, image) .then( (result) => { if ( (result !== null) && (result.image !== null) && (result.url !== null) ) { var linkelem = insertOrChangeLinkElement(result.image.ownerDocument, result.image.parentNode, result.url); var divmatch = result.image.ownerDocument.evaluate("./ancestor::div[(contains(@class,'hide') or contains(@class,'earlycomments')) and ancestor::div[contains(@class,'post')]]", linkelem, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); var divelem = divmatch.singleNodeValue; divelem.style = "border: 5px solid grey;"; divelem.setAttribute("title", getSizeText(result.size)); getImageDimensionsPromise(result.url, divelem, result.size) .then( (result2) => { result2.element.style = "border: 5px solid " + result2.color + ";"; result2.element.setAttribute("title", getSizeText(result2.size) + " - " + result2.width + " x " + result2.height); }); }; }); } } } // multi-image posts - unhide all images (instead of having to manually click on "show x more images" matches2 = myDoc.evaluate(".//div[contains(@class,'image_container') or contains(@class,'image_content')]" + "/div[contains(@style,'display:none')]", el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var j=0, node; (j<matches2.snapshotLength); j++) { node=matches2.snapshotItem(j); if (node) { node.style.display = "initial"; } } // multi-image posts - hide the "show x more images" element matches2 = myDoc.evaluate(".//div[contains(@class,'image_container') or contains(@class,'image_content')]" + "/following-sibling::div[contains(@class,'viewAll')]", el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var j=0, node; (j<matches2.snapshotLength); j++) { node=matches2.snapshotItem(j); if (node) { node.style.display = "none"; } } } catch (e) { console.warn("error: ", e); } } } } // try to find the timestamp info and display in upper right corner function displayTimestamps(myDoc, myContext) { //console.info("displayTimestamps: ", myContext); if (myDoc===null) myDoc = myContext; if (myDoc===null) return; if (myContext===null) myContext = myDoc; var matches; var tmpstr; var singlematch; var postinfo; var timestamp; var newnode; matches = myDoc.evaluate("./descendant-or-self::div[contains(@class,'feed') and @title]", myContext, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0, el; (i<matches.snapshotLength); i++) { el = matches.snapshotItem(i); if (el) { try { timestamp = el.getAttribute("title"); if (timestamp && timestamp.length>5 && timestamp.length<70) { singlematch = myDoc.evaluate(".//div[contains(@class,'post_info')]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); postinfo = singlematch.singleNodeValue; if (postinfo) { newnode = myDoc.createElement("div"); newnode.setAttribute("style", "float:right; margin-right: 10px; padding-right: 10px;"); newnode.innerHTML = timestamp; postinfo.appendChild(newnode); } } } catch (e) { console.warn("error: ", e); } } } } function getOriginalPosterImageURL(imageurl, originalposter) { if (originalposter === null) { return imageurl; } var pos = imageurl.toLowerCase().indexOf(".bdsmlr.com"); var pos2 = originalposter.toLowerCase().indexOf(".bdsmlr.com"); if (pos > 0 && pos2 > 0) { return originalposter.substring(0, pos2) + imageurl.substring(pos); } else { return imageurl; } } function isOriginalImageURL(imageurl) { if (imageurl === null) { return false; } var tmpstr = imageurl.toLowerCase(); if (tmpstr.includes("bdsmlr.com/")) { var pos = tmpstr.lastIndexOf("."); var pos2 = tmpstr.lastIndexOf("-og."); if (pos > 0 && (pos2+3)==pos) { return true; } } return false; } function getOriginalImageURL(imageurl) { if (imageurl === null) { return imageurl; } var pos = imageurl.lastIndexOf("."); var pos2 = imageurl.lastIndexOf("-og."); if (pos > 0 && (pos2+3)!=pos) { return imageurl.substring(0, pos) + "-og" + imageurl.substring(pos); } else { return imageurl; } } function insertOrChangeLinkElement(myDoc, wrapElement, linkTarget) { if (wrapElement.tagName.toLowerCase() == "a") { wrapElement.setAttribute("href", linkTarget); wrapElement.setAttribute("target", "_blank"); return wrapElement; } else { var parentNode = wrapElement.parentNode; var newNode = myDoc.createElement("a"); newNode.setAttribute("href", linkTarget); newNode.setAttribute("target", "_blank"); parentNode.replaceChild(newNode, wrapElement); newNode.appendChild(wrapElement); return newNode; } } function getSizeText(sizeInBytes) { if (sizeInBytes === null) { return ""; } if (sizeInBytes >= 1048576) { return (sizeInBytes / 1048576).toFixed(1) + " MB"; } else if (sizeInBytes >= 1024) { return (sizeInBytes / 1024).toFixed(0) + " KB"; } else { return sizeInBytes.toFixed(0) + " B"; } } // This ASYNC method returns a promise to retrieve the HTTP response header data for the supplied URL. // It uses an "HTTP HEAD" request which does NOT download the response payload (to minimize network traffic) async function checkUrlHeaderOnlyPromise(url, allowredirect) { return fetch(url, (allowredirect ? { redirect: 'follow', method: 'HEAD' } : { redirect: 'error', method: 'HEAD' } ) ).then( function(response) { if (response.ok) { var contentLength = parseInt(response.headers.get('Content-Length'), 10); if (isNaN(contentLength)) contentLength = 0; if (response.redirected && allowredirect) { return { url: response.url, size: contentLength } } else { return { url: url, size: contentLength }; } } else { return { url: url, size: -1 }; } }, function(rejectreason) { return { url: url, size: -1 }; }); } // This ASYNC method returns a promise to determine the url of the originally uploaded image (the one with a suffix of "-og" in thre name) // This requires "testing" a lot of URLs, i.e. it causes server traffic (especially if it is an image on the "old" CDN servers from BDSMLR's early times). // To minimize network traffic, this method only requests the HTTP headers for all these URLs - the image itself nor error webpages (404) are fully downloaded. // If multiple "-og" variants are found, the onre with the largest size (in bytes, not image dimensions!) is chosen. // If the og version is smaller than the non-og version it still sticks with the og (the non-og is typically upscaled and worse quality than the og) // "imageelement" is only passed-through - it is a helper to supply the DOM context to the surrounding asynchronous promise then function of the caller async function getBestImageUrlPromise(imageurl, linkurl, blogurl, imageelement) { var knownImagePromise; var urlsToCheckPromises = []; var matches; var imageurl_cdnnum; var linkurl_cdnnum; var imageurl_cdnnumstr; var linkurl_cdnnumstr; var imageurl_path; var linkurl_path; var blogurl_base; var knownImageResult; var bestImageUrl; var bestImageSize; var bestImageIsOG; //console.info("checkAllUrlheaders-Enter: ", "imageurl:" + imageurl + " linkurl:" + linkurl + " blogurl:" + blogurl); // get CDN image server number as int and as string matches = imageurl.toLowerCase().match("https?:\/\/cdno?(0?[1-9][0-9]*)\.bdsmlr\.com\/"); if (matches !== null) { imageurl_cdnnumstr = matches[1]; imageurl_cdnnum = parseInt(matches[1], 10); } else { imageurl_cdnnum = NaN; } // get CDNO image server number as int and as string matches = linkurl.toLowerCase().match("https?:\/\/cdno?(0?[1-9][0-9]*)\.bdsmlr\.com\/"); if (matches !== null) { linkurl_cdnnumstr = matches[1]; linkurl_cdnnum = parseInt(matches[1], 10); } else { linkurl_cdnnum = NaN; } // get non-hostname part of the url, including leading "/" (NOTE: fixes buggy urls with multiple "//" after the hostname) matches = imageurl.toLowerCase().match("https?:\/\/[^.]*\.?bdsmlr\.com\/*(\/.+$)"); if (matches !== null) { imageurl_path = matches[1]; } else { imageurl_path = null; } // get non-hostname part of the url, including leading "/" (NOTE: fixes buggy urls with multiple "//" after the hostname) matches = linkurl.toLowerCase().match("https?:\/\/[^.]*\.?bdsmlr\.com\/*(\/.+$)"); if (matches !== null) { linkurl_path = matches[1]; } else { linkurl_path = null; } // get base hostname for the originating blog if (blogurl !== null) { matches = blogurl.toLowerCase().match("https?:\/\/([^./]+\.bdsmlr\.com)"); if (matches !== null) { blogurl_base = matches[1]; } else { blogurl_base = null; } } else { blogurl_base = null; } // fetch promise for the image that is currently shown in the webpage (ALLOW redirection on this URL) knownImagePromise = checkUrlHeaderOnlyPromise(imageurl, true); // image to which the current unmodified link in the webpage is pointing to (ALLOW redirection on this URL) urlsToCheckPromises.push(checkUrlHeaderOnlyPromise(linkurl, true)); // Use a Set to collect all other URLs that are to be tested - this automatically eliminates dups that the URL selection logic below might create var uniqueset = new Set(); // append "-og" suffix to the original image and link urls (without modifying the hostname) uniqueset.add(getOriginalImageURL(imageurl)); uniqueset.add(getOriginalImageURL(linkurl)); if (isNaN(imageurl_cdnnum) || imageurl_cdnnum <= 5) { //old CDN servers (cdn02 - cdn05, cdno02 - cdno05 or non-cdn location) // -> "Wild West" as to where the "-og" image variant might be "hiding" uniqueset.add(getOriginalImageURL("https://bdsmlr.com" + imageurl_path)); if (blogurl_base !== null) { uniqueset.add(getOriginalImageURL("https://" + blogurl_base + imageurl_path)); } uniqueset.add(getOriginalImageURL("https://cdn02.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdn03.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdn04.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdn05.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdno02.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdno03.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdno04.bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdno05.bdsmlr.com" + imageurl_path)); } if (isNaN(linkurl_cdnnum) || linkurl_cdnnum <= 5) { //old CDN servers (cdn02 - cdn05, cdno02 - cdno05 or non-cdn location) // -> "Wild West" as to where the "-og" image variant might be "hiding" uniqueset.add(getOriginalImageURL("https://bdsmlr.com" + linkurl_path)); if (blogurl_base !== null) { uniqueset.add(getOriginalImageURL("https://" + blogurl_base + linkurl_path)); } uniqueset.add(getOriginalImageURL("https://cdn02.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdn03.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdn04.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdn05.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdno02.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdno03.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdno04.bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdno05.bdsmlr.com" + linkurl_path)); } if (!isNaN(imageurl_cdnnum) && imageurl_cdnnum > 5) { //new CDN servers (cdn06+) uniqueset.add(getOriginalImageURL("https://cdn" + imageurl_cdnnumstr + ".bdsmlr.com" + imageurl_path)); uniqueset.add(getOriginalImageURL("https://cdno" + imageurl_cdnnumstr + ".bdsmlr.com" + imageurl_path)); } if (!isNaN(linkurl_cdnnum) && linkurl_cdnnum > 5) { //new CDN servers (cdn06+) uniqueset.add(getOriginalImageURL("https://cdn" + linkurl_cdnnumstr + ".bdsmlr.com" + linkurl_path)); uniqueset.add(getOriginalImageURL("https://cdno" + linkurl_cdnnumstr + ".bdsmlr.com" + linkurl_path)); } for (var str of uniqueset) { // some BDSMLR image servers have faulty redirections that are circular - causing huge delay and not resulting in anything useful // Thus, all URLs in the Set are to be tested WITHOUT allowing redirection urlsToCheckPromises.push(checkUrlHeaderOnlyPromise(str, false)); } // Get the data for the image currently shown in the webpage as starting point knownImageResult = await knownImagePromise; bestImageUrl = knownImageResult.url; bestImageSize = knownImageResult.size; bestImageIsOG = isOriginalImageURL(bestImageUrl); // wait until all URLs have resolved (i.e. have their HTTP headers loaded with image or error info) await Promise.allSettled(urlsToCheckPromises). then( (results) => results.forEach((result) => { // If this result is a better image than currently known - replace and use this one as next best known image if ( (isOriginalImageURL(result.value.url) && !bestImageIsOG && (result.value.size > 0)) || ( (result.value.size > bestImageSize) && (isOriginalImageURL(result.value.url) == bestImageIsOG) ) ) { bestImageSize = result.value.size; bestImageUrl = result.value.url; bestImageIsOG = isOriginalImageURL(bestImageUrl); } })); return {url: bestImageUrl, size: bestImageSize, isOG: bestImageIsOG, image: imageelement}; } // This ASYNC method get the natural dimensions of the supplied image // This means the image needs to be downloaded fully, unfortunately // Thus, a delay is to be expected, except if the image is already cached // Depending on the image height,m the method suggests a "markup color" and then discards the downloaded image again. // "imageelement" is only passed-through - it is a helper to supply the DOM context to the surrounding asynchronous promise then function of the caller async function getImageDimensionsPromise(imageurl, divelement, imagesize) { var image; var imageH; var imageW; var color; // sanity check - skip full download of image if it is larger than 20MB if ( (imagesize !== null) && (imagesize > 20971520) ) { return {url: imageurl, element: divelement, width: "unknown", height: "unknown", color: "Grey", size:imagesize}; } image = new Image(); image.src = imageurl; await image.decode().then(function() { imageH = image.naturalHeight; imageW = image.naturalWidth; }); image.src = "data:,"; // clear the image now that we no longer need it if (imageH >= 2160) { color = "SpringGreen"; } else if (imageH >= 1080) { color = "Green"; } else if (imageH >= 810) { color = "YellowGreen"; } else if (imageH >= 540) { color = "Yellow"; } else if (imageH >=270 ) { color = "Orange"; } else if (imageH < 270 && imageH > 0 ) { color = "Red"; } else { color = "Grey"; }; return {url: imageurl, element: divelement, width: imageW, height: imageH, color: color, size:imagesize}; } //------------------------------------------------------------ // FIRST PART OF SCRIPT #1 - initial statement and registration of event observer //------------------------------------------------------------ // script runs NOT in the context of an image - i.e. dashboard, blog stream, individual post // -> register event observer for future to-be-loaded posts (endless scrolling) and execute first part of script (createImageLinks & displayTimestamps) on already loaded posts // fix buggy redirection from BDSMLR if ( window.location.href.includes('bdsmlr.com//') ) { var tmpstr = window.location.href; var pos = tmpstr.indexOf('bdsmlr.com//'); // remove the double // window.location.assign( tmpstr.substring(0, pos+11) + tmpstr.substring(pos+12) ); } // prevent running for URL to image or media else if ( !(window.location.href.includes('bdsmlr.com/uploads/')) ) { // create an observer instance and iterate through each individual new node var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(addedNode) { createImageLinks(mutation.target.ownerDocument, addedNode); displayTimestamps(mutation.target.ownerDocument, addedNode); }); }); }); // configuration of the observer // NOTE: subtree is false as the wanted nodes are direct children of <div class="newsfeed"> -> notable performance improvement // "theme1" is the class used by the feed root node for individual user's blog (xxxx.bdsmlr.com) -> seems unstable/temporary name -> might be changed by bdsmlr var config = { attributes: false, childList: true, characterData: false, subtree: false }; // pass in the target node (<div> element contains all stream posts), as well as the observer options var postsmatch = document.evaluate(".//div[contains(@class,'newsfeed')] | .//div[contains(@class,'theme1')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); var postsnode = postsmatch.singleNodeValue; //process already loaded nodes (the initial posts before scrolling down for the first time) createImageLinks(document, postsnode); displayTimestamps(document, postsnode); //start the observer for new nodes observer.observe(postsnode, config); // also observe the right sidebar blog stream on the dashboard // pass in the target node, as well as the observer options (subtree has to be true here - target nodes are further down the hierarchy) var config2 = { attributes: false, childList: true, characterData: false, subtree: true }; var sidepostsmatch = document.evaluate(".//div[@id='rightposts']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); var sidepostsnode = sidepostsmatch.singleNodeValue; // sidebar does only exist on dashboard if (sidepostsnode) { //start the observer for overlays observer.observe(sidepostsnode, config2); } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址