BDSMLR - clickable links to original high-res images

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.

目前為 2019-01-19 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         BDSMLR - clickable links to original high-res images
// @namespace    bdsmlr_linkify
// @version      1.2.0
// @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://greasyfork.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/*
// @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 postlink;
  var origbloglink;
  var origblog;
  var imagematches;
  var imageurl;

  matches = myDoc.evaluate(".//div[contains(@class,'postholder')] | .//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 on individual blogs (xxx.bdsmlr.com) and there's also no orig poster info on individual posts :-(
			  singlematch = myDoc.evaluate(".//div[contains(@class,'originalposter')]/a[contains(@href,'.bdsmlr.com/post/')] | " +
                                     ".//a[(contains(@class,'adata') or contains(@class,'ndata')) and contains(@href,'.bdsmlr.com/post/')]",
                                     el, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        postlink = singlematch.singleNodeValue; // xxxx.bdsmlr.com/post/yyyyyyyy

        if (postlink) {
          origblog = postlink.getAttribute("href");
        } else {
          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; // zzzz.bdsmlr.com
          if (origbloglink) {
          	origblog = origbloglink.getAttribute("href");
          } else {
            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 = image.getAttribute("src");
              // No idea why this is needed... DevTools inspector always show a valid image src attribute... but at script exercution time... apparently not... seems to be some bdsmlr JavaScript post-processing...
              if (imageurl === null) {
                imageurl = image.getAttribute("data-echo"); 
              }  
            }
            if (imageurl) {
              if (origblog && origblog.length>0) {
                // 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);
              } else {
                // if no original poster info - construct link to "-og" version of image on the current blog (e.g. works for non-retweets on individual blogs)
                tmpstr = getOriginalImageURL(imageurl);
              }
              // get the link node
              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/photos/')) {
  
  if (document.head.textContent && document.head.textContent.toLowerCase().includes('404 not found') ) {
    var tmpstr = window.location.href;
    var pos = tmpstr.lastIndexOf(".");
    var pos2 = tmpstr.lastIndexOf("-og.");
    var pos3 = tmpstr.indexOf(".");

    if (pos == pos2+3) {
      // if this is a failed attempt to retreive an "-og" version of an image -> revert to non-"-og" version on cdn02.bdsmlr.com
      window.location.assign("https://cdn03" + tmpstr.substring(pos3,pos2) + tmpstr.substring(pos));
    }
    else if (tmpstr.startsWith("https://cdn03")) {
      window.location.assign("https://cdn02" + tmpstr.substring(pos3));
    }
  }
  
}
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);
  }

}