// ==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)); } })();