// ==UserScript==
// @name Instagram Download Button
// @name:zh-TW Instagram 下載器
// @namespace https://github.com/y252328/Instagram_Download_Button
// @version 1.3.1
// @compatible chrome
// @compatible firefox
// @compatible edge
// @description Add download button and open button to download or open media in the posts, stories and highlights in Instagram
// @description:zh-TW 在Instagram頁面加入下載按鈕與開啟按鈕,透過這些按鈕可以下載或開啟貼文、限時動態及Highlight中的照片或影片
// @author ZhiYu
// @match https://www.instagram.com/*
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
function yyyymmdd(date) {
// ref: https://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object?page=1&tab=votes#tab-top
var mm = date.getMonth() + 1; // getMonth() is zero-based
var dd = date.getDate();
return [date.getFullYear(),
(mm > 9 ? '' : '0') + mm,
(dd > 9 ? '' : '0') + dd
].join('');
};
var svgDownloadBtn =
``;
var svgNewtabBtn =
``;
var checkExistTimer = setInterval(function () {
let lang = document.getElementsByTagName("html")[0].getAttribute('lang');
let sharePostSelector = "section > button > svg";
let menuSeletor = "header button > span";
// check story
if (document.getElementsByClassName("custom-btn").length === 0) {
if (document.querySelector(menuSeletor)) {
addCustomBtn(document.querySelector(menuSeletor), "white");
}
}
// check post
let articleList = document.querySelectorAll("article");
for (let i = 0; i < articleList.length; i++) {
if (articleList[i].querySelector(sharePostSelector) &&
articleList[i].getElementsByClassName("custom-btn").length === 0) {
addCustomBtn(articleList[i].querySelector(sharePostSelector), "black");
}
}
}, 500);
function addCustomBtn(node, iconColor) {
// add download button to post or story page and set onclick handler
// add newtab button
let newtabBtn = document.createElement("span");
newtabBtn.innerHTML = svgNewtabBtn.replace('%color', iconColor);
newtabBtn.setAttribute("class", "custom-btn newtab-btn");
newtabBtn.setAttribute("title", "open in new tab");
newtabBtn.setAttribute("style", "cursor: pointer;margin-left: 16px;margin-top: 8px;");
newtabBtn.onclick = function () {
customBtnClicked(newtabBtn);
}
node.parentNode.parentNode.appendChild(newtabBtn);
// add download button
let downloadBtn = document.createElement("span");
downloadBtn.innerHTML = svgDownloadBtn.replace('%color', iconColor);
downloadBtn.setAttribute("class", "custom-btn download-btn");
downloadBtn.setAttribute("title", "download");
downloadBtn.setAttribute("style", "cursor: pointer;margin-left: 14px;margin-top: 8px;");
downloadBtn.onclick = function () {
customBtnClicked(downloadBtn);
}
node.parentNode.parentNode.appendChild(downloadBtn);
}
function customBtnClicked(target) {
// handle download button click
if (window.location.pathname.includes('stories')) {
handleStory(target);
} else {
handlePost(target);
}
}
function handlePost(target) {
// extract url from target post and download or open it
let articleNode = target;
while (articleNode && articleNode.tagName !== "ARTICLE") {
articleNode = articleNode.parentNode;
}
let list = articleNode.querySelectorAll('li[style][class]');
let url = "";
let filename = "";
// =====================
// = extract media url =
// =====================
if (list.length == 0) {
// single img or video
if (document.querySelector('article div > video')) {
url = document.querySelector('article div > video').getAttribute('src');
} else if (document.querySelector('article div[role] div > img')) {
url = document.querySelector('article div[role] div > img').getAttribute('src');
}
} else {
// multiple imgs or videos
let idx = 0;
// check current index
if (!document.querySelector('.coreSpriteLeftChevron')) {
idx = 0;
} else if (!document.querySelector('.coreSpriteRightChevron')) {
idx = list.length - 1;
} else idx = 1;
let node = list[idx];
if (node.querySelector('video')) {
url = node.querySelector('video').getAttribute('src');
} else if (node.querySelector('img')) {
url = node.querySelector('img').getAttribute('src');
}
}
// ==============================
// = download or open media url =
// ==============================
if (url.length > 0) {
// check url
if (target.getAttribute("class").includes("download-btn")) {
// generate filename
// add time to filename
let datetime = new Date(articleNode.querySelector('time').getAttribute('datetime'))
filename = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '') + '-' + filename;
// add poster name to filename
let posterName = articleNode.querySelector('header a').getAttribute('href').replace(/\//g, '');
filename = posterName + '-' + filename;
// download
downloadResource(url, filename);
} else {
// open url in new tab
openResource(url);
}
}
}
function handleStory(target) {
// extract url from target story and download or open it
let url = ""
// =====================
// = extract media url =
// =====================
if (document.querySelector('video > source')) {
url = document.querySelector('video > source').getAttribute('src');
} else if (document.querySelector('img[decoding="sync"]')) {
url = document.querySelector('img[decoding="sync"]').getAttribute('src');
}
let filename = url.split('?')[0].split('\\').pop().split('/').pop();
// ==============================
// = download or open media url =
// ==============================
if (target.getAttribute("class").includes("download-btn")) {
// generate filename
// add time to filename
let datetime = new Date(document.querySelector('time').getAttribute('datetime'))
filename = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '') + '-' + filename;
// add poster name to filename
let posterName = document.querySelector('header a').getAttribute('href').replace(/\//g, '');
filename = posterName + '-' + filename;
// download
downloadResource(url, filename);
} else {
// open url in new tab
openResource(url);
}
}
function openResource(url) {
// open url in new tab
var a = document.createElement('a');
a.href = url;
a.setAttribute("target", "_blank");
document.body.appendChild(a);
a.click();
a.remove();
}
function forceDownload(blob, filename) {
// ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
var a = document.createElement('a');
a.download = filename;
a.href = blob;
// For Firefox https://stackoverflow.com/a/32226068
document.body.appendChild(a);
a.click();
a.remove();
}
// Current blob size limit is around 500MB for browsers
function downloadResource(url, filename) {
// ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
if (!filename) filename = url.split('\\').pop().split('/').pop();
fetch(url, {
headers: new Headers({
'Origin': location.origin
}),
mode: 'cors'
})
.then(response => response.blob())
.then(blob => {
let blobUrl = window.URL.createObjectURL(blob);
forceDownload(blobUrl, filename);
})
.catch(e => console.error(e));
}
})();