Greasy Fork is available in English.

Instagram Download Button

Add download button and open button to download or open media in the posts, stories and highlights in Instagram

2020-07-09 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

  1. // ==UserScript==
  2. // @name Instagram Download Button
  3. // @name:zh-TW Instagram 下載器
  4. // @namespace https://github.com/y252328/Instagram_Download_Button
  5. // @version 1.3.0
  6. // @compatible chrome
  7. // @compatible firefox
  8. // @compatible edge
  9. // @description Add download button and open button to download or open media in the posts, stories and highlights in Instagram
  10. // @description:zh-TW 在Instagram頁面加入下載按鈕與開啟按鈕,透過這些按鈕可以下載或開啟貼文、限時動態及Highlight中的照片或影片
  11. // @author ZhiYu
  12. // @match https://www.instagram.com/*
  13. // @grant none
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19. function yyyymmdd(date) {
  20. // ref: https://stackoverflow.com/questions/3066586/get-string-in-yyyymmdd-format-from-js-date-object?page=1&tab=votes#tab-top
  21. var mm = date.getMonth() + 1; // getMonth() is zero-based
  22. var dd = date.getDate();
  23.  
  24. return [date.getFullYear(),
  25. (mm > 9 ? '' : '0') + mm,
  26. (dd > 9 ? '' : '0') + dd
  27. ].join('');
  28. };
  29.  
  30. var svgDownloadBtn =
  31. `<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" height="24" width="24"
  32. viewBox="0 0 477.867 477.867" style="fill:%color;" xml:space="preserve">
  33. <g>
  34. <path d="M443.733,307.2c-9.426,0-17.067,7.641-17.067,17.067v102.4c0,9.426-7.641,17.067-17.067,17.067H68.267
  35. c-9.426,0-17.067-7.641-17.067-17.067v-102.4c0-9.426-7.641-17.067-17.067-17.067s-17.067,7.641-17.067,17.067v102.4
  36. c0,28.277,22.923,51.2,51.2,51.2H409.6c28.277,0,51.2-22.923,51.2-51.2v-102.4C460.8,314.841,453.159,307.2,443.733,307.2z"/>
  37. </g>
  38. <g>
  39. <path d="M335.947,295.134c-6.614-6.387-17.099-6.387-23.712,0L256,351.334V17.067C256,7.641,248.359,0,238.933,0
  40. s-17.067,7.641-17.067,17.067v334.268l-56.201-56.201c-6.78-6.548-17.584-6.36-24.132,0.419c-6.388,6.614-6.388,17.099,0,23.713
  41. l85.333,85.333c6.657,6.673,17.463,6.687,24.136,0.031c0.01-0.01,0.02-0.02,0.031-0.031l85.333-85.333
  42. C342.915,312.486,342.727,301.682,335.947,295.134z"/>
  43. </g>
  44. </svg>`;
  45.  
  46. var svgNewtabBtn =
  47. `<svg id="Capa_1" style="fill:%color;" viewBox="0 0 482.239 482.239" xmlns="http://www.w3.org/2000/svg" height="24" width="24">
  48. <path d="m465.016 0h-344.456c-9.52 0-17.223 7.703-17.223 17.223v86.114h-86.114c-9.52 0-17.223 7.703-17.223 17.223v344.456c0 9.52 7.703 17.223 17.223 17.223h344.456c9.52 0 17.223-7.703 17.223-17.223v-86.114h86.114c9.52 0 17.223-7.703 17.223-17.223v-344.456c0-9.52-7.703-17.223-17.223-17.223zm-120.56 447.793h-310.01v-310.01h310.011v310.01zm103.337-103.337h-68.891v-223.896c0-9.52-7.703-17.223-17.223-17.223h-223.896v-68.891h310.011v310.01z"/>
  49. </svg>`;
  50.  
  51. var checkExistTimer = setInterval(function () {
  52. let lang = document.getElementsByTagName("html")[0].getAttribute('lang');
  53. let sharePostSelector = "section > button > svg";
  54. let menuSeletor = "header button > span";
  55.  
  56. // check story
  57. if (document.getElementsByClassName("custom-btn").length == 0) {
  58. if (document.querySelector(menuSeletor)) {
  59. addCustomBtn(document.querySelector(menuSeletor), "white");
  60. }
  61. }
  62.  
  63. // check post
  64. let articleList = document.querySelectorAll("article");
  65. for (let i = 0; i < articleList.length; i++) {
  66. if (articleList[i].querySelector(sharePostSelector) &&
  67. articleList[i].getElementsByClassName("custom-btn").length == 0) {
  68. addCustomBtn(articleList[i].querySelector(sharePostSelector), "black");
  69. }
  70. }
  71.  
  72. }, 500);
  73.  
  74. function addCustomBtn(node, iconColor) {
  75. // add download button to post or story page and set onclick handler
  76. // add newtab button
  77. let newtabBtn = document.createElement("span");
  78. newtabBtn.innerHTML = svgNewtabBtn.replace('%color', iconColor);
  79. newtabBtn.setAttribute("class", "custom-btn newtab-btn");
  80. newtabBtn.setAttribute("title", "open in new tab");
  81. newtabBtn.setAttribute("style", "cursor: pointer;margin-left: 16px;margin-top: 8px;");
  82. newtabBtn.onclick = function () {
  83. customBtnClicked(newtabBtn);
  84. }
  85. node.parentNode.parentNode.appendChild(newtabBtn);
  86.  
  87. // add download button
  88. let downloadBtn = document.createElement("span");
  89. downloadBtn.innerHTML = svgDownloadBtn.replace('%color', iconColor);
  90. downloadBtn.setAttribute("class", "custom-btn download-btn");
  91. downloadBtn.setAttribute("title", "download");
  92. downloadBtn.setAttribute("style", "cursor: pointer;margin-left: 14px;margin-top: 8px;");
  93. downloadBtn.onclick = function () {
  94. customBtnClicked(downloadBtn);
  95. }
  96. node.parentNode.parentNode.appendChild(downloadBtn);
  97. }
  98.  
  99. function customBtnClicked(target) {
  100. // handle download button click
  101. if (window.location.pathname.includes('stories')) {
  102. handleStory(target);
  103. } else {
  104. handlePost(target);
  105. }
  106. }
  107.  
  108. function handlePost(target) {
  109. // extract url from target post and download or open it
  110. let articleNode = target;
  111. while (articleNode && articleNode.tagName !== "ARTICLE") {
  112. articleNode = articleNode.parentNode;
  113. }
  114. let list = articleNode.querySelectorAll('li[style][class]');
  115. let url = "";
  116. let filename = "";
  117.  
  118. // =====================
  119. // = extract media url =
  120. // =====================
  121. if (list.length == 0) {
  122. // single img or video
  123. if (document.querySelector('article div > video')) {
  124. url = document.querySelector('article div > video').getAttribute('src');
  125. } else if (document.querySelector('article div[role] div > img')) {
  126. url = document.querySelector('article div[role] div > img').getAttribute('src');
  127. }
  128. } else {
  129. // multiple imgs or videos
  130. let idx = 0;
  131. // check current index
  132. if (!document.querySelector('.coreSpriteLeftChevron')) {
  133. idx = 0;
  134. } else if (!document.querySelector('.coreSpriteRightChevron')) {
  135. idx = list.length - 1;
  136. } else idx = 1;
  137.  
  138. let node = list[idx];
  139. if (node.querySelector('video')) {
  140. url = node.querySelector('video').getAttribute('src');
  141. } else if (node.querySelector('img')) {
  142. url = node.querySelector('img').getAttribute('src');
  143. }
  144. }
  145.  
  146. // ==============================
  147. // = download or open media url =
  148. // ==============================
  149. if (url.length > 0) {
  150. // check url
  151. if (target.getAttribute("class").includes("download-btn")) {
  152. // generate filename
  153. // add time to filename
  154. let datetime = new Date(articleNode.querySelector('time').getAttribute('datetime'))
  155. filename = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '') + '-' + filename;
  156. // add poster name to filename
  157. let posterName = articleNode.querySelector('header a').getAttribute('href').replace(/\//g, '');
  158. filename = posterName + '-' + filename;
  159.  
  160. // download
  161. downloadResource(url, filename);
  162. } else {
  163. // open url in new tab
  164. openResource(url);
  165. }
  166. }
  167. }
  168.  
  169. function handleStory(target) {
  170. // extract url from target story and download or open it
  171. let url = ""
  172.  
  173. // =====================
  174. // = extract media url =
  175. // =====================
  176. if (document.querySelector('video > source')) {
  177. url = document.querySelector('video > source').getAttribute('src');
  178. } else if (document.querySelector('img[decoding="sync"]')) {
  179. url = document.querySelector('img[decoding="sync"]').getAttribute('src');
  180. }
  181. let filename = url.split('?')[0].split('\\').pop().split('/').pop();
  182.  
  183. // ==============================
  184. // = download or open media url =
  185. // ==============================
  186. if (target.getAttribute("class").includes("download-btn")) {
  187. // generate filename
  188. // add time to filename
  189. let datetime = new Date(document.querySelector('time').getAttribute('datetime'))
  190. filename = yyyymmdd(datetime) + '_' + datetime.toTimeString().split(' ')[0].replace(/:/g, '') + '-' + filename;
  191. // add poster name to filename
  192. let posterName = document.querySelector('header a').getAttribute('href').replace(/\//g, '');
  193. filename = posterName + '-' + filename;
  194.  
  195. // download
  196. downloadResource(url, filename);
  197. } else {
  198. // open url in new tab
  199. openResource(url);
  200. }
  201. }
  202.  
  203. function openResource(url) {
  204. // open url in new tab
  205. var a = document.createElement('a');
  206. a.href = url;
  207. a.setAttribute("target", "_blank");
  208. document.body.appendChild(a);
  209. a.click();
  210. a.remove();
  211. }
  212.  
  213. function forceDownload(blob, filename) {
  214. // ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
  215. var a = document.createElement('a');
  216. a.download = filename;
  217. a.href = blob;
  218. // For Firefox https://stackoverflow.com/a/32226068
  219. document.body.appendChild(a);
  220. a.click();
  221. a.remove();
  222. }
  223.  
  224. // Current blob size limit is around 500MB for browsers
  225. function downloadResource(url, filename) {
  226. // ref: https://stackoverflow.com/questions/49474775/chrome-65-blocks-cross-origin-a-download-client-side-workaround-to-force-down
  227. if (!filename) filename = url.split('\\').pop().split('/').pop();
  228. fetch(url, {
  229. headers: new Headers({
  230. 'Origin': location.origin
  231. }),
  232. mode: 'cors'
  233. })
  234. .then(response => response.blob())
  235. .then(blob => {
  236. let blobUrl = window.URL.createObjectURL(blob);
  237. forceDownload(blobUrl, filename);
  238. })
  239. .catch(e => console.error(e));
  240. }
  241. })();