// ==UserScript==
// @name Instagram Downloader
// @namespace https://lawrenzo.com/p/direct-picture-link
// @version 0.5
// @description Direct image links for sites that obfuscate the image from easy downloading.
// @author Lawrence Sim
// @license WTFPL (http://www.wtfpl.net)
// @match https://*.instagram.com/*
// @grant none
// ==/UserScript==
(function() {
var contextMenu,
styles = {
'position': 'absolute',
'background': '#efefef',
'border-radius': '0.2em',
'box-shadow': '1px 1px 3px 1px rgba(0,0,0,0.4)',
'padding': '0.2em 0.5em',
'font-size': '0.85em',
'z-index': '999999'
};
var openContextMenu = (evt, src, type) => {
let a;
if(!contextMenu) {
contextMenu = document.createElement("div");
for(let key in styles) contextMenu.style[key] = styles[key];
a = document.createElement("a");
a.innerHTML = "> view direct source";
a.setAttribute("target", "_blank");
contextMenu.append(a);
document.body.append(contextMenu);
}
a = a || contextMenu.querySelector("a");
a.setAttribute("href", src);
if(type === "video") {
a.setAttribute("download", "video");
a.innerHTML = "> view direct video";
} else {
a.setAttribute("download", "");
a.innerHTML = "> view direct image";
}
contextMenu.style.left = `${evt.pageX}px`;
contextMenu.style.top = `${evt.pageY}px`;
};
document.body.addEventListener("click", () => {
if(!contextMenu) return;
contextMenu.remove();
contextMenu = null;
});
var postRegExp = /https?:\/\/(?:www\.)?instagram\.com\/p\/([A-Za-z0-9\-\_]+)\//,
mediaUrlMap = {};
var getMediaUrls = (postID, onComplete) => {
return new Promise((resolve, reject) => {
if(mediaUrlMap[postID]) {
resolve(mediaUrlMap[postID]);
return;
}
var req = new XMLHttpRequest();
req.onerror = () => reject(req);
req.onreadystatechange = () => {
if(req.readyState == XMLHttpRequest.DONE && req.status == 200) {
let json, shortcodeMedia;
try {
json = JSON.parse(req.responseText);
shortcodeMedia = json.graphql.shortcode_media;
} catch(e) { }
if(!shortcodeMedia) return;
let mediaUrls;
switch(shortcodeMedia.__typename) {
case 'GraphImage':
mediaUrls = [shortcodeMedia.display_url];
break;
case 'GraphVideo':
mediaUrls = [shortcodeMedia.video_url];
break;
default:
var edges = (shortcodeMedia.edge_sidecar_to_children && shortcodeMedia.edge_sidecar_to_children.edges) || [];
mediaUrls = edges.map(edge => {
if(!edge.node.is_video) return edge.node.display_url;
let url = edge.node.video_url;
return "https://scontent"+url.slice(url.indexOf(".cdninstagram.com"));
});
break;
}
mediaUrlMap[postID] = mediaUrls;
resolve(mediaUrlMap[postID]);
}
};
req.open("GET", "https://www.instagram.com/p/"+postID+"/?__a=1");
req.send();
});
};
var link = (mutated, observer) => {
mutated = mutated || [{target: document.body}];
let matchPost = window.location.href.match(postRegExp);
matchPost ? linkPost(mutated, matchPost[1]) : linkFeed(mutated);
};
var linkPicture = (img, src, multi) => {
let btn = multi ? multi.querySelector("div[role='button']") : img.closest("div[role='button']");
if(!btn) return;
btn.addEventListener("contextmenu", evt => {
evt.preventDefault();
openContextMenu(evt, src || img.getAttribute("src"));
});
img.setAttribute("ilnkd", 1);
};
var linkVideo = (vid, src, multi) => {
let btn = multi ? multi.firstChild : vid.parentNode.parentNode.parentNode.parentNode;
if(!btn) return;
btn.addEventListener("contextmenu", evt => {
evt.preventDefault();
openContextMenu(evt, src, "video");
});
vid.setAttribute("ilnkd", 1);
};
var linkFeed = mutated => {
mutated.forEach(mutant => {
mutant.target.querySelectorAll("article img").forEach(img => {
if(img.getAttribute("ilnkd")) return;
linkPicture(img, img.getAttribute("src"), img.closest("li > div"));
});
//mutant.target.querySelectorAll("article video").forEach(vid => {
// if(vid.getAttribute("ilnkd")) return;
// linkVideo(vid, vid.getAttribute("src"), vid.closest("li > div"));
//});
});
};
var linkPost = async function(mutated, postID, onComplete) {
var mediaUrls = await getMediaUrls(postID);
mutated.forEach(mutant => {
mutant.target.querySelectorAll("li").forEach(li => {
let media = li.querySelector("img, video");
if(!media || media.getAttribute("ilnkd") !== null) return;
let thisIndex = 0;
[li.previousSibling, li.nextSibling].find((sibling, i) => {
if(!sibling) return;
let siblingMedia = sibling && sibling.querySelector("img, video"),
nearIndex = parseInt(siblingMedia && siblingMedia.getAttribute("ilnkd") || -1);
if(~nearIndex) {
thisIndex = nearIndex + (i ? -1 : 1);
return true;
}
});
if(media.tagName === "IMG") {
linkPicture(media, mediaUrls[thisIndex], media.closest("li > div"));
} else {
linkVideo(media, mediaUrls[thisIndex], media.closest("li > div"));
}
media.setAttribute("ilnkd", thisIndex);
});
let media = mutant.target.querySelector("article img, article video");
if(media.getAttribute("ilnkd")) return;
if(media.tagName === "IMG") {
linkPicture(media, mediaUrls[0]);
} else {
linkVideo(media, mediaUrls[0]);
}
});
};
link();
var obs = new MutationObserver(link);
obs.observe(document.body, {childList:true, subtree:true});
})();