您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Modifies images to link to their original ("-og") version. Works for (a) the dashboard, (b) blogs displayed on right sidebar in the dashboard, (c) blog streams (xxx.bdsmlr.com) and (d) individual posts (xxx.bdsmlr.com/post/yyyyyyyyyy). It does NOT work for the archive view.
当前为
// ==UserScript== // @name BDSMLR - clickable links to original high-res images // @namespace bdsmlr_linkify // @version 1.10.1 // @license GNU AGPLv3 // @description Modifies images to link to their original ("-og") version. Works for (a) the dashboard, (b) blogs displayed on right sidebar in the dashboard, (c) blog streams (xxx.bdsmlr.com) and (d) individual posts (xxx.bdsmlr.com/post/yyyyyyyyyy). It does NOT work for the archive view. // @author marp // @homepageURL https://gf.zukizuki.org/en/users/204542-marp // @include https://bdsmlr.com/ // @include https://bdsmlr.com/dashboard // @include https://*.bdsmlr.com/ // @include https://*.bdsmlr.com/post/* // @include https://bdsmlr.com/uploads/photos/* // @include https://bdsmlr.com/uploads/pictures/* // @include https://*.bdsmlr.com/uploads/photos/* // @include https://*.bdsmlr.com/uploads/pictures/* // @include https://bdsmlr.com//uploads/* // @run-at document-end // ==/UserScript== function createImageLinks(myDoc, myContext) { //console.info("createImageLinks: ", myContext); if (myDoc===null) myDoc = myContext; if (myDoc===null) return; if (myContext===null) myContext = myDoc; var matches; var tmpstr; var singlematch; var origpostlink; var origbloglink; var origblog; var imagematches; var imageurl; 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 :-( singlematch = myDoc.evaluate(".//div[contains(@class,'originalposter')]/a[contains(@href,'.bdsmlr.com/post/')]", el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); origpostlink = singlematch.singleNodeValue; // xxxx.bdsmlr.com/post/yyyyyyyy if (origpostlink) { origblog = origpostlink.getAttribute("href"); //everything after and including "/post" gets truncated away later anyway } else { origblog = null; } if (origblog === null) { //second method might find the originial blog URL (xxxx.bdsmlr.com) 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") ) ) { 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") ) ) { 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; } else { // however - if the current url is the dashboard then we're out of luck origblog = 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 imagematches = 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", el, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); for(var j=0, image, imagelink; (j<imagematches.snapshotLength); j++) { image=imagematches.snapshotItem(j); if (image) { imagelink = image.parentNode; imageurl = imagelink.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 exercution 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) { tmpstr = getOriginalImageURL(imageurl); // check that the current image url does NOT already points to the -og version on cdn04 (in this case "-og" gets added by the method - thus different length) if ( (imageurl.length != tmpstr.length) || !(imageurl.toLowerCase().includes("https://cdn04.bdsmlr.com/")) ) { // if we have the url of the original blog then we use a different mechanism to reconstruct the orig image URL // otherwise we stay with tmpstr as is if (origblog && origblog.length > 5) { // if we have info about original poster -> construct link to "-og" version of image on orig posters blog (e.g. https://<origposter>.bdsmlr.com/<....>/imagename-og.jpg) tmpstr = getOriginalPosterImageURL(imageurl, origblog); tmpstr = getOriginalImageURL(tmpstr); } } // get the link node and set the link target image.parentNode.setAttribute("href", tmpstr); } } } } 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 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; } } // Two very different actions depending on if this is on twitter.com or twing.com if (window.location.href.includes('bdsmlr.com/uploads/')) { if (document.head.textContent && ( document.head.textContent.toLowerCase().includes('404 not found') || document.head.textContent.toLowerCase().includes('403 forbidden') ) ) { var tmpstr = window.location.href; var pos = tmpstr.lastIndexOf("."); var pos2 = tmpstr.lastIndexOf("-og."); var pos3 = tmpstr.indexOf("."); if (pos == pos2+3) { // old mechanism - -og to be found only using server/blog name of the original poster // seems this didn't work -> now try to find -og on the cdn servers if (tmpstr.startsWith("https://bdsmlr.com")) { window.location.assign("https://cdn04.bdsmlr" + tmpstr.substring(pos3)); } else if (!tmpstr.startsWith("https://cdn")) { window.location.assign("https://" + tmpstr.substring(pos3+1)); } else if (tmpstr.startsWith("https://cdn03")) { window.location.assign("https://cdn02" + tmpstr.substring(pos3)); } else if (tmpstr.startsWith("https://cdn04")) { window.location.assign("https://cdn03" + tmpstr.substring(pos3)); } else { // seems there's no -og version to be found -> revert to non-"-og" version on cdn04.bdsmlr.com window.location.assign("https://cdn04" + tmpstr.substring(pos3,pos2) + tmpstr.substring(pos)); } } else if (tmpstr.startsWith("https://cdn04")) { // look for non-"og" version on cdn03 window.location.assign("https://cdn03" + tmpstr.substring(pos3)); } else if (tmpstr.startsWith("https://cdn03")) { // look for non-"og" version on cdn02 window.location.assign("https://cdn02" + tmpstr.substring(pos3)); } } } // fix bug in bdsmlr redirection url else if (window.location.href.includes('bdsmlr.com//uploads/')) { var tmpstr = window.location.href; var pos = tmpstr.lastIndexOf("//"); window.location.assign(tmpstr.substring(0,pos) + tmpstr.substring(pos+1)); } else { // 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); }); }); }); // 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); //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 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, config); } }