IG小助手

一键下载对方 Instagram 帖子中的相片、视频甚至是他们的快拍!

当前为 2022-10-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name IG Helper
  3. // @name:zh-TW IG小精靈
  4. // @name:zh-CN IG小助手
  5. // @name:ja IG助手
  6. // @name:ko IG조수
  7. // @namespace https://github.snkms.com/
  8. // @version 2.5.0
  9. // @description Downloading Instagram posts photos and videos or their stories!
  10. // @description:zh-TW 一鍵下載對方 Instagram 貼文中的相片、影片甚至是他們的限時動態!
  11. // @description:zh-CN 一键下载对方 Instagram 帖子中的相片、视频甚至是他们的快拍!
  12. // @description:ja 写真、ビデオ、そしてお互いの Instagram 投稿からのストーリーずズのワンクリックダウンロード!
  13. // @description:ko Instagram 게시물에서 사진, 비디오 또는 이야기를 다운로드하십시오.
  14. // @author SN-Koarashi (5026)
  15. // @match https://*.instagram.com/*
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_xmlhttpRequest
  19. // @require https://code.jquery.com/jquery-3.6.0.min.js
  20. // @supportURL https://www.facebook.com/smileopwe/
  21. // @icon https://www.google.com/s2/favicons?domain=www.instagram.com
  22. // @compatible firefox >= 87
  23. // @compatible chrome >= 90
  24. // @compatible edge >= 90
  25. // @license GPLv3
  26. // ==/UserScript==
  27.  
  28. (function(jQuery) {
  29. 'use strict';
  30. // Icon download by https://www.flaticon.com/authors/pixel-perfect
  31.  
  32. const $ = jQuery;
  33. const checkInterval = 250;
  34. const lang = navigator.language || navigator.userLanguage;
  35.  
  36. var currentURL = location.href;
  37. var currentHeight = $(document).height();
  38. var firstStarted = false;
  39. var pageLoaded = false;
  40.  
  41. var GL_postPath;
  42. var GL_username;
  43. var GL_repeat
  44.  
  45. // Main Timer
  46. var timer = setInterval(function(){
  47. currentHeight = $(document).height();
  48.  
  49. // Call Instagram dialog function if url changed.
  50. if(currentURL != location.href || !firstStarted || !pageLoaded){
  51. clearInterval(GL_repeat);
  52. pageLoaded = false;
  53. firstStarted = true;
  54. currentURL = location.href;
  55.  
  56. if(location.href.startsWith("https://www.instagram.com/p/")){
  57. console.log('isDialog');
  58. setTimeout(()=>{
  59. onReadyMyDW(false);
  60. },150);
  61. pageLoaded = true;
  62. }
  63.  
  64. if(location.href == "https://www.instagram.com/"){
  65. console.log('isHomepage');
  66. setTimeout(()=>{
  67. onReadyMyDW(false);
  68. },150);
  69. pageLoaded = true;
  70. }
  71. if(!$('body > div div.x9f619 div._adqx[data-visualcompletion="loading-state"]').length && $('canvas._aarh').length && location.href.match(/^(https:\/\/www\.instagram\.com\/)([0-9A-Za-z\.\-_]+)\/?$/ig) && !location.href.match(/^(https:\/\/www\.instagram\.com\/(stories|explore)\/?)/ig)){
  72. console.log('isProfile');
  73. setTimeout(()=>{
  74. onProfileDW(false);
  75. },150);
  76. pageLoaded = true;
  77. }
  78.  
  79. if(!pageLoaded){
  80. // Call Instagram stories function
  81. if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/highlights\/)/ig)){
  82. console.log('isHighlightsStory');
  83. onHighlightsStoryDW(false);
  84. GL_repeat = setInterval(()=>{
  85. onHighlightsStoryThumbnailDW(false);
  86. },checkInterval);
  87.  
  88. if($(".IG_DWHISTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  89. }
  90. else if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/)/ig)){
  91. console.log('isStory');
  92. onStoryDW(false);
  93. onStoryThumbnailDW(false);
  94.  
  95. if($(".IG_DWSTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  96. }
  97. else{
  98. pageLoaded = false;
  99. // Remove the download icon
  100. $('.IG_DWSTORY').remove();
  101. $('.IG_DWSTORY_THUMBNAIL').remove();
  102. }
  103. }
  104.  
  105. }
  106.  
  107. // Direct Download Checkbox
  108. if(!$('.AutoDownload_dom').length){
  109. let ckValue = (GM_getValue('AutoDownload'))?'checked':'';
  110. $('body div.mfclru0v.astyfpdk.om3e55n1.jez8cy9q').css('position','relative');
  111. $('body div.mfclru0v.astyfpdk.om3e55n1.jez8cy9q').append(`<div class="AutoDownload_dom" style="position:absolute;left:10px;bottom:7px;padding:0px;line-height:1;display:inline-block;width:fit-content;"><label title="${_i18n("DDL_INTRO")}" style="cursor:help;"><input type="checkbox" value="1" class="AutoDownload" name="AutoDownload" ${ckValue} />${_i18n("DDL")}</label></div>`);
  112. }
  113. },checkInterval);
  114.  
  115. // Call general function when user scroll the page
  116. $(document).scroll(function(){
  117. if(currentHeight != $(this).height() && location.href == "https://www.instagram.com/"){
  118. onReadyMyDW();
  119. }
  120. });
  121.  
  122. // Profile funcion
  123. async function onProfileDW(isDownload){
  124. if(isDownload){
  125. let date = new Date().getTime();
  126. let timestamp = Math.floor(date / 1000);
  127. let username = location.href.replace(/\/$/ig,'').split('/').at(-1);
  128. let userInfo = await getUserId(username);
  129.  
  130. saveFiles(userInfo.user.profile_pic_url,username,"avatar",timestamp,'jpg');
  131. }
  132. else{
  133. // Add the stories download button
  134. let style = "position: absolute;right:0px;top:0px;padding:5px;line-height:1;background:#fff;border-radius: 50%;cursor:pointer;border: 1px solid #ccc";
  135. if(!$('.IG_DWPROFILE').length){
  136. $('body > div main canvas._aarh').parent().append(`<div title="${_i18n("DW")}" class="IG_DWPROFILE" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><path d="M382.56,233.376C379.968,227.648,374.272,224,368,224h-64V16c0-8.832-7.168-16-16-16h-64c-8.832,0-16,7.168-16,16v208h-64 c-6.272,0-11.968,3.68-14.56,9.376c-2.624,5.728-1.6,12.416,2.528,17.152l112,128c3.04,3.488,7.424,5.472,12.032,5.472 c4.608,0,8.992-2.016,12.032-5.472l112-128C384.192,245.824,385.152,239.104,382.56,233.376z"/></g></g><g><g><path d="M432,352v96H80v-96H16v128c0,17.696,14.336,32,32,32h416c17.696,0,32-14.304,32-32V352H432z"/></g></g></div>`);
  137. return true;
  138. }
  139. }
  140. }
  141.  
  142. // Highlight Stories funcion
  143. async function onHighlightsStoryDW(isDownload){
  144. if(isDownload){
  145. let date = new Date().getTime();
  146. let timestamp = Math.floor(date / 1000);
  147. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  148. let highStories = await getHighlightsStories(highlightId);
  149. let username = highStories.data.reels_media[0].owner.username;
  150. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length;
  151. let totIndex = highStories.data.reels_media[0].items.length;
  152.  
  153. let target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  154. if(target.is_video){
  155. saveFiles(target.video_resources.at(-1).src,username,"highlights",timestamp,'mp4');
  156. }
  157. else{
  158. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg');
  159. }
  160. }
  161. else{
  162. // Add the stories download button
  163. let style = "position: absolute;right:-40px;top:15px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  164. if(!$('.IG_DWHISTORY').length){
  165. $('body > div section._ac0a').append(`<div title="${_i18n("DW")}" class="IG_DWHISTORY" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><path d="M382.56,233.376C379.968,227.648,374.272,224,368,224h-64V16c0-8.832-7.168-16-16-16h-64c-8.832,0-16,7.168-16,16v208h-64 c-6.272,0-11.968,3.68-14.56,9.376c-2.624,5.728-1.6,12.416,2.528,17.152l112,128c3.04,3.488,7.424,5.472,12.032,5.472 c4.608,0,8.992-2.016,12.032-5.472l112-128C384.192,245.824,385.152,239.104,382.56,233.376z"/></g></g><g><g><path d="M432,352v96H80v-96H16v128c0,17.696,14.336,32,32,32h416c17.696,0,32-14.304,32-32V352H432z"/></g></g></div>`);
  166. return true;
  167. }
  168. }
  169. }
  170.  
  171. // Highlight Stories Thumbnail funcion
  172. async function onHighlightsStoryThumbnailDW(isDownload){
  173. if(isDownload){
  174. let date = new Date().getTime();
  175. let timestamp = Math.floor(date / 1000);
  176. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  177. let highStories = await getHighlightsStories(highlightId);
  178. let username = highStories.data.reels_media[0].owner.username;
  179. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length;
  180. let totIndex = highStories.data.reels_media[0].items.length;
  181.  
  182. let target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  183. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg');
  184. }
  185. else{
  186. if($('body > div section._ac0a video.xh8yej3').length){
  187. // Add the stories download button
  188. let style = "position: absolute;right:-40px;top:45px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  189. if(!$('.IG_DWHISTORY_THUMBNAIL').length){
  190. $('body > div section._ac0a').append(`<div title="${_i18n("THUMBNAIL_INTRO")}" class="IG_DWHISTORY_THUMBNAIL" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512"><circle cx="8.25" cy="5.25" r=".5"/><path d="m8.25 6.5c-.689 0-1.25-.561-1.25-1.25s.561-1.25 1.25-1.25 1.25.561 1.25 1.25-.561 1.25-1.25 1.25zm0-1.5c-.138 0-.25.112-.25.25 0 .275.5.275.5 0 0-.138-.112-.25-.25-.25z"/><path d="m7.25 11.25 2-2.5 2.25 1.5 2.25-3.5 3 4.5z"/><path d="m16.75 12h-9.5c-.288 0-.551-.165-.676-.425s-.09-.568.09-.793l2-2.5c.243-.304.678-.372 1.002-.156l1.616 1.077 1.837-2.859c.137-.212.372-.342.625-.344.246-.026.49.123.63.334l3 4.5c.153.23.168.526.037.77-.13.244-.385.396-.661.396zm-4.519-1.5h3.118l-1.587-2.381zm-3.42 0h1.712l-1.117-.745z"/><path d="m22.25 14h-2.756c-.778 0-1.452.501-1.676 1.247l-.859 2.862c-.16.533-.641.891-1.197.891h-7.524c-.556 0-1.037-.358-1.197-.891l-.859-2.861c-.224-.747-.897-1.248-1.676-1.248h-2.756c-.965 0-1.75.785-1.75 1.75v5.5c0 1.517 1.233 2.75 2.75 2.75h18.5c1.517 0 2.75-1.233 2.75-2.75v-5.5c0-.965-.785-1.75-1.75-1.75z"/><path d="m4 12c-.552 0-1-.448-1-1v-8c0-1.654 1.346-3 3-3h12c1.654 0 3 1.346 3 3v8c0 .552-.448 1-1 1s-1-.448-1-1v-8c0-.551-.449-1-1-1h-12c-.551 0-1 .449-1 1v8c0 .552-.448 1-1 1z"/></svg></div>`);
  191. }
  192. }
  193. else{
  194. $('.IG_DWHISTORY_THUMBNAIL').remove();
  195. }
  196. }
  197. }
  198.  
  199. // Stories funcion
  200. async function onStoryDW(isDownload){
  201. if(isDownload){
  202. let date = new Date().getTime();
  203. let timestamp = Math.floor(date / 1000);
  204. let username = $("body > div section._ac0a header._ac0k ._ac0l div ._ac0q > a").text();
  205.  
  206. if($('body > div section._ac0a video.xh8yej3').length){
  207. // Download stories if it is video
  208. let type = "mp4";
  209. let userInfo = await getUserId(username);
  210. let userId = userInfo.user.pk;
  211. let stories = await getStories(userId);
  212.  
  213. let targetURL = location.href.replace(/\/$/ig,'').split("/").at(-1);
  214. let videoURL = "";
  215.  
  216. stories.data.reels_media[0].items.forEach(item => {
  217. if(item.id == targetURL){
  218. videoURL = item.video_resources[0].src;
  219. }
  220. });
  221.  
  222. if(videoURL.length == 0){
  223. alert(_i18n("NO_VID_URL"));
  224. }
  225. else{
  226. saveFiles(videoURL,username,"stories",timestamp,type);
  227. }
  228.  
  229. }
  230. else{
  231. // Download stories if it is image
  232. let srcset = $('section._ac0a ._aa64 img._aa63').attr('srcset').split(',')[0].split(' ')[0];
  233. let link = (srcset)?srcset:$('section._ac0a ._aa64 img._aa63').attr('src');
  234.  
  235. let downloadLink = link;
  236. let type = 'jpg';
  237.  
  238. saveFiles(downloadLink,username,"stories",timestamp,type);
  239. }
  240. }
  241. else{
  242. // Add the stories download button
  243. let style = "position: absolute;right:-40px;top:15px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  244. if(!$('.IG_DWSTORY').length){
  245. $('body > div section._ac0a').append(`<div title="${_i18n("DW")}" class="IG_DWSTORY" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><path d="M382.56,233.376C379.968,227.648,374.272,224,368,224h-64V16c0-8.832-7.168-16-16-16h-64c-8.832,0-16,7.168-16,16v208h-64 c-6.272,0-11.968,3.68-14.56,9.376c-2.624,5.728-1.6,12.416,2.528,17.152l112,128c3.04,3.488,7.424,5.472,12.032,5.472 c4.608,0,8.992-2.016,12.032-5.472l112-128C384.192,245.824,385.152,239.104,382.56,233.376z"/></g></g><g><g><path d="M432,352v96H80v-96H16v128c0,17.696,14.336,32,32,32h416c17.696,0,32-14.304,32-32V352H432z"/></g></g></div>`);
  246. return true;
  247. }
  248. }
  249. }
  250.  
  251. // Stories Thumbnail funcion
  252. async function onStoryThumbnailDW(isDownload){
  253. if(isDownload){
  254. // Download stories if it is video
  255. let date = new Date().getTime();
  256. let timestamp = Math.floor(date / 1000);
  257. let type = 'jpg';
  258. let username = $("body > div section._ac0a header._ac0k ._ac0l div ._ac0q > a").text();
  259. let style = 'margin:5px 0px;padding:5px 0px;color:#111;font-size:1rem;line-height:1rem;text-align:center;border:1px solid #000;border-radius: 5px;';
  260. // Download thumbnail
  261. let userInfo = await getUserId(username);
  262. let userId = userInfo.user.pk;
  263. let stories = await getStories(userId);
  264. let targetURL = location.href.replace(/\/$/ig,'').split("/").at(-1);
  265. let videoThumbnailURL = "";
  266.  
  267. stories.data.reels_media[0].items.forEach(item => {
  268. if(item.id == targetURL){
  269. videoThumbnailURL = item.display_url;
  270. console.log(item.display_url);
  271. }
  272. });
  273.  
  274. saveFiles(videoThumbnailURL,username,"thumbnail",timestamp,type);
  275. }
  276. else{
  277. if($('body > div section._ac0a video.xh8yej3').length){
  278. // Add the stories download button
  279. let style = "position: absolute;right:-40px;top:45px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  280. if(!$('.IG_DWSTORY_THUMBNAIL').length){
  281. $('body > div section._ac0a').append(`<div title="${_i18n("THUMBNAIL_INTRO")}" class="IG_DWSTORY_THUMBNAIL" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512"><circle cx="8.25" cy="5.25" r=".5"/><path d="m8.25 6.5c-.689 0-1.25-.561-1.25-1.25s.561-1.25 1.25-1.25 1.25.561 1.25 1.25-.561 1.25-1.25 1.25zm0-1.5c-.138 0-.25.112-.25.25 0 .275.5.275.5 0 0-.138-.112-.25-.25-.25z"/><path d="m7.25 11.25 2-2.5 2.25 1.5 2.25-3.5 3 4.5z"/><path d="m16.75 12h-9.5c-.288 0-.551-.165-.676-.425s-.09-.568.09-.793l2-2.5c.243-.304.678-.372 1.002-.156l1.616 1.077 1.837-2.859c.137-.212.372-.342.625-.344.246-.026.49.123.63.334l3 4.5c.153.23.168.526.037.77-.13.244-.385.396-.661.396zm-4.519-1.5h3.118l-1.587-2.381zm-3.42 0h1.712l-1.117-.745z"/><path d="m22.25 14h-2.756c-.778 0-1.452.501-1.676 1.247l-.859 2.862c-.16.533-.641.891-1.197.891h-7.524c-.556 0-1.037-.358-1.197-.891l-.859-2.861c-.224-.747-.897-1.248-1.676-1.248h-2.756c-.965 0-1.75.785-1.75 1.75v5.5c0 1.517 1.233 2.75 2.75 2.75h18.5c1.517 0 2.75-1.233 2.75-2.75v-5.5c0-.965-.785-1.75-1.75-1.75z"/><path d="m4 12c-.552 0-1-.448-1-1v-8c0-1.654 1.346-3 3-3h12c1.654 0 3 1.346 3 3v8c0 .552-.448 1-1 1s-1-.448-1-1v-8c0-.551-.449-1-1-1h-12c-.551 0-1 .449-1 1v8c0 .552-.448 1-1 1z"/></svg></div>`);
  282. }
  283. }
  284. else{
  285. $('.IG_DWSTORY_THUMBNAIL').remove();
  286. }
  287. }
  288. }
  289.  
  290. // Prepare promise to fetch user stories
  291. function getHighlightsStories(highlightId){
  292. return new Promise((resolve,reject)=>{
  293. let getURL = `https://www.instagram.com/graphql/query/?query_hash=45246d3fe16ccc6577e0bd297a5db1ab&variables=%7B%22highlight_reel_ids%22:%5B%22${highlightId}%22%5D,%22precomposed_overlay%22:false%7D`;
  294.  
  295. GM_xmlhttpRequest({
  296. method: "GET",
  297. url: getURL,
  298. onload: function(response) {
  299. let obj = JSON.parse(response.response);
  300. resolve(obj);
  301. },
  302. onerror: function(err){
  303. reject(err);
  304. }
  305. });
  306. });
  307. }
  308.  
  309. // Prepare promise to fetch user stories
  310. function getStories(userId){
  311. return new Promise((resolve,reject)=>{
  312. let getURL = `https://www.instagram.com/graphql/query/?query_hash=15463e8449a83d3d60b06be7e90627c7&variables=%7B%22reel_ids%22:%5B%22${userId}%22%5D,%22precomposed_overlay%22:false%7D`;
  313.  
  314. GM_xmlhttpRequest({
  315. method: "GET",
  316. url: getURL,
  317. onload: function(response) {
  318. let obj = JSON.parse(response.response);
  319. resolve(obj);
  320. },
  321. onerror: function(err){
  322. reject(err);
  323. }
  324. });
  325. });
  326. }
  327.  
  328. // Prepare promise to fetch user id by username
  329. function getUserId(username){
  330. return new Promise((resolve,reject)=>{
  331. let getURL = `https://www.instagram.com/web/search/topsearch/?query=${username}`;
  332.  
  333. GM_xmlhttpRequest({
  334. method: "GET",
  335. url: getURL,
  336. onload: function(response) {
  337. let obj = JSON.parse(response.response);
  338. resolve(obj.users[0]);
  339. },
  340. onerror: function(err){
  341. reject(err);
  342. }
  343. });
  344. });
  345. }
  346.  
  347. // Prepare promise to cache article which contains blob media
  348. function getBlobMedia(postPath){
  349. return new Promise((resolve,reject)=>{
  350. if(!postPath) reject("NOPATH");
  351. let postShortCode = postPath.substring(3, postPath.length - 1);
  352. let getURL = `https://www.instagram.com/graphql/query/?query_hash=2c4c2e343a8f64c625ba02b2aa12c7f8&variables=%7B%22shortcode%22:%22${postShortCode}%22}`;
  353.  
  354. GM_xmlhttpRequest({
  355. method: "GET",
  356. url: getURL,
  357. onload: function(response) {
  358. let obj = JSON.parse(response.response);
  359. resolve(obj.data);
  360. },
  361. onerror: function(err){
  362. reject(err);
  363. }
  364. });
  365. });
  366. }
  367.  
  368. // Main function
  369. function onReadyMyDW(NoDialog){
  370. // Whether is Instagram dialog?
  371. if(!NoDialog){
  372. // Running if it is dialog
  373. $('article[role="presentation"]').each(function(){
  374. $(this).removeAttr('data-snig');
  375. $(this).unbind('click');
  376. });
  377. $('.SNKMS_IG_DW_MAIN,.SNKMS_IG_DW_MAIN_VIDEO').remove();
  378. }
  379. if(NoDialog == false){
  380. var repeat = setInterval(() => {
  381. if($('article[data-snig="canDownload"]').length > 0) clearInterval(repeat);
  382. createArtBtn();
  383. },250);
  384. }
  385. else{
  386. createArtBtn();
  387. }
  388. }
  389.  
  390. function createArtBtn(){
  391. // Add download icon per each posts
  392. $('article[role="presentation"]').each(function(){
  393. // If it is have not download icon
  394. if(!$(this).attr('data-snig')){
  395. console.log("Found article");
  396. var style = "position: absolute;right:15px;top:15px;padding:6px;line-height:1;background:#fff;border-radius: 50%;cursor:pointer;";
  397.  
  398. // Add the download icon
  399. let $childElement = $(this).children("div").children("div");
  400. $childElement.eq($childElement.length - 2).append(`<div title="${_i18n("DW")}" class="SNKMS_IG_DW_MAIN" style="${style}"><svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><path d="M382.56,233.376C379.968,227.648,374.272,224,368,224h-64V16c0-8.832-7.168-16-16-16h-64c-8.832,0-16,7.168-16,16v208h-64 c-6.272,0-11.968,3.68-14.56,9.376c-2.624,5.728-1.6,12.416,2.528,17.152l112,128c3.04,3.488,7.424,5.472,12.032,5.472 c4.608,0,8.992-2.016,12.032-5.472l112-128C384.192,245.824,385.152,239.104,382.56,233.376z"/></g></g><g><g><path d="M432,352v96H80v-96H16v128c0,17.696,14.336,32,32,32h416c17.696,0,32-14.304,32-32V352H432z"/></g></g></div>`);
  401.  
  402. // Running if user click the download icon
  403. $(this).on('click','.SNKMS_IG_DW_MAIN', async function(e){
  404. GL_username = $(this).parent().parent().parent().attr('data-username');
  405. GL_postPath = $(this).parent().parent().children("div:last-child").children("div").find("div > section").last().prev().find("a").attr("href");
  406.  
  407. // Create element that download dailog
  408. IG_createDM(GM_getValue('AutoDownload'));
  409.  
  410. $("#article-id").text(GL_postPath);
  411. var style = 'display:block;margin:5px 0px;padding:5px 0px;color:#111;font-size:1rem;line-height:1rem;text-align:center;border:1px solid #000;border-radius: 5px;';
  412.  
  413. // Find video/image element and add the download icon
  414. var s = 0;
  415. var multiple = $(this).parent().parent().find('._aap0 ._acaz').length;
  416. var pathname = window.location.pathname;
  417. var fullpathname = "/"+pathname.split('/')[1]+"/"+pathname.split('/')[2]+"/";
  418.  
  419. // If posts have more than one images or videos.
  420. if(multiple){
  421. var blob = false;
  422. $(this).parent().find('._aap0 ._acaz').each(function(){
  423. let element_videos = $(this).parent().parent().find('video._ab1d');
  424. if(element_videos && element_videos.attr('src') && element_videos.attr('src').match(/^blob:/ig)){
  425. blob = true;
  426. }
  427. });
  428.  
  429.  
  430. if(blob){
  431. createMediaCacheDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",style,_i18n("LOAD_BLOB_MULTIPLE"));
  432. }
  433. else{
  434. let blob = false;
  435. $(this).parent().find('._aap0 ._acaz').each(function(){
  436. s++;
  437. let element_videos = $(this).find('video._ab1d');
  438. let element_images = $(this).find('._aagv img');
  439. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  440.  
  441. if(element_videos && element_videos.attr('src')){
  442. let video_image = element_videos.attr('poster');
  443. let video_url = element_videos.attr('src');
  444.  
  445. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-name="IGTV" data-type="mp4" data-globalIndex="${s}" style="${style}" href="javascript:;" data-href="${video_url}"><img width="100" src="${video_image}" /><br/>- ${_i18n("VID")} ${s} -</a>`);
  446.  
  447. if(video_url.match(/^blob:/ig)) blob = true;
  448. }
  449. if(element_images && imgLink){
  450. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-name="photo" data-type="jpg" data-globalIndex="${s}" style="${style}" href="javascript:;" data-href="${imgLink}"><img width="100" src="${imgLink}" /><br/>- ${_i18n("IMG")} ${s} -</a>`);
  451. }
  452.  
  453. });
  454.  
  455. if(blob){
  456. createMediaCacheDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",style,_i18n("LOAD_BLOB_RELOAD"));
  457. }
  458. }
  459. }
  460. else{
  461. s++;
  462. let element_videos = $(this).parent().parent().find('video._ab1d');
  463. let element_images = $(this).parent().parent().find('._aagv img');
  464. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  465.  
  466.  
  467. if(element_videos && element_videos.attr('src')){
  468. let video_image = element_videos.attr('poster');
  469. let video_url = element_videos.attr('src');
  470.  
  471. if(element_videos.attr('src').match(/^blob:/ig)){
  472. createMediaCacheDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",style,_i18n("LOAD_BLOB_ONE"));
  473. }
  474. else{
  475. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-name="video" data-type="mp4" data-globalIndex="${s}" style="${style}" href="javascript:;" data-href="${video_url}"><img width="100" src="${video_image}" /><br/>- ${_i18n("VID")} ${s} -</a>`);
  476. }
  477. }
  478. if(element_images && imgLink){
  479. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-name="photo" data-type="jpg" data-globalIndex="${s}" style="${style}" href="javascript:;" href="" data-href="${imgLink}"><img width="100" src="${imgLink}" /><br/>- ${_i18n("IMG")} ${s} -</a>`);
  480. }
  481. }
  482.  
  483.  
  484. if(GM_getValue('AutoDownload')){
  485. var autoTimer = setInterval(()=>{
  486. if($('a[data-type]').length > 0){
  487. var GB_Index = 1;
  488.  
  489. if(!$('a[data-blob="true"]').length){
  490. let selectionArr = [...$(this).parent().find('._aamk').children('._acnb')];
  491.  
  492. let LeftButton = $(this).parent().parent().find('button._aahh').length;
  493. let RightButton = $(this).parent().parent().find('button._aahi').length;
  494.  
  495. if(LeftButton && !RightButton){ // Left Only
  496. GB_Index = 2;
  497. console.log('Left Only');
  498. }
  499. else if(!LeftButton && RightButton){ // Right Only
  500. GB_Index = 1;
  501. console.log('Right Only');
  502. }
  503. else if(!LeftButton && !RightButton){ // Both Not Exist
  504. GB_Index = 1;
  505. console.log('Both Not Exist');
  506. }
  507. else{ // Both Exist
  508. GB_Index = 2;
  509. console.log('Both Exist');
  510. }
  511. }
  512. else{
  513. console.log('Blob Media');
  514. let selectionArr = ($(this).parent().find('._aamk').children('._acnb').length)?[...$(this).parent().find('._aamk').children('._acnb')]:[...$(this).parent().find('._aamj').children('._acnb')];
  515. let idx = 0;
  516.  
  517. for(let element of selectionArr){
  518. idx++;
  519. if($(element).attr('class').match(/_acnf/g)){
  520. GB_Index = idx;
  521. }
  522. }
  523. }
  524.  
  525. let downloadLink = $('.IG_SN_DIG').find('a[data-globalindex="'+GB_Index+'"]').attr('data-href');
  526. let date = new Date().getTime();
  527. let timestamp = Math.floor(date / 1000);
  528. let type = $('.IG_SN_DIG').find('a[data-globalindex="'+GB_Index+'"]').attr('data-type');
  529. let name = $('.IG_SN_DIG').find('a[data-globalindex="'+GB_Index+'"]').attr('data-name');
  530.  
  531. saveFiles(downloadLink,GL_username,name,timestamp,type);
  532. $('.IG_SN_DIG').remove();
  533. clearInterval(autoTimer);
  534. }
  535. },500);
  536. }
  537. });
  538.  
  539. // Add the mark that download is ready
  540. var username = $(this).find("header div:last-child span > a").text();
  541.  
  542. $(this).attr('data-snig','canDownload');
  543. $(this).attr('data-username',username);
  544. }
  545. });
  546. }
  547.  
  548. // Create media element from blob media
  549. async function createMediaCacheDOM(postURL,selector,style,message){
  550. $(`${selector} a`).remove();
  551. $(selector).append('<p style="text-align:center;font-size:20px;" id="_SNLOAD">'+ message +'</p>');
  552. let media = await getBlobMedia(postURL);
  553. let idx = 1;
  554. let resource = media.shortcode_media;
  555.  
  556. // GraphVideo
  557. if(resource.__typename == "GraphVideo" && resource.video_url){
  558. $(selector).append(`<a data-blob="true" data-needed="direct" data-name="video" data-type="mp4" data-globalIndex="${idx}" style="${style}" href="javascript:;" data-href="${resource.video_url}"><img width="100" src="${resource.display_resources[1].src}" /><br/>- ${_i18n("VID")} ${idx} -</a>`);
  559. idx++;
  560. }
  561. // GraphSidecar
  562. if(resource.__typename == "GraphSidecar" && resource.edge_sidecar_to_children){
  563. for(let e of resource.edge_sidecar_to_children.edges){
  564. if(e.node.__typename == "GraphVideo"){
  565. $(selector).append(`<a data-blob="true" data-needed="direct" data-name="video" data-type="mp4" data-globalIndex="${idx}" style="${style}" href="javascript:;" data-href="${e.node.video_url}"><img width="100" src="${e.node.display_resources[1].src}" /><br/>- ${_i18n("VID")} ${idx} -</a>`);
  566. }
  567.  
  568. if(e.node.__typename == "GraphImage"){
  569. $(selector).append(`<a data-blob="true" data-needed="direct" data-name="photo" data-type="jpg" data-globalIndex="${idx}" style="${style}" href="javascript:;" data-href="${e.node.display_resources[e.node.display_resources.length - 1].src}"><img width="100" src="${e.node.display_resources[1].src}" /><br/>- ${_i18n("IMG")} ${idx} -</a>`);
  570. }
  571. idx++;
  572. }
  573. }
  574. $("#_SNLOAD").remove();
  575. }
  576.  
  577. // Create the download dialog element funcion
  578. function IG_createDM(a){
  579. let style = (!a)?"position: fixed;left: 0px;right: 0px;bottom: 0px;top: 0px;":"display:none;";
  580. $('body').append('<div class="IG_SN_DIG" style="'+style+';z-index: 500;"><div class="IG_SN_DIG_BG" style="'+style+'z-index:502;background: rgba(0,0,0,.75);"></div><div class="IG_SN_DIG_MAIN" style="z-index: 510;padding:10px 15px;top:7%;position: absolute;left: 50%;transform: translateX(-50%);width: 500px;background:#fff;border-radius: 7px;"><div class="IG_SN_DIG_TITLE"></div><div style="min-height: 100px;max-height: 80vh;overflow-y:auto;" class="IG_SN_DIG_BODY"></div></div></div>');
  581. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE').append('<div style="position:relative;height:36px;text-align:center;"><div style="position:absolute;left:0px;line-height: 18px;">Alt+Q ['+_i18n("CLOSE")+']</div><div style="line-height: 18px;">IG Helper</div><div style="line-height: 14px;font-size:14px;">Article: <span id="article-id"></span></div><svg width="26" height="26" class="IG_SN_DIG_BTN" style="cursor:pointer;position:absolute;right:0px;top:0px;" xmlns="http://www.w3.org/2000/svg" id="bold" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512"><path d="m14.828 12 5.303-5.303c.586-.586.586-1.536 0-2.121l-.707-.707c-.586-.586-1.536-.586-2.121 0l-5.303 5.303-5.303-5.304c-.586-.586-1.536-.586-2.121 0l-.708.707c-.586.586-.586 1.536 0 2.121l5.304 5.304-5.303 5.303c-.586.586-.586 1.536 0 2.121l.707.707c.586.586 1.536.586 2.121 0l5.303-5.303 5.303 5.303c.586.586 1.536.586 2.121 0l.707-.707c.586-.586.586-1.536 0-2.121z"/></svg></div>');
  582. }
  583.  
  584. // Download and rename files
  585. function saveFiles(downloadLink,username,index,timestamp,type){
  586. fetch(downloadLink).then(res => {
  587. return res.blob().then(dwel => {
  588. const a = document.createElement("a");
  589. const name = username+'-'+index+'-'+timestamp+'.'+type;
  590. a.href = URL.createObjectURL(dwel);
  591. a.setAttribute("download", name);
  592. a.click();
  593. a.remove();
  594. });
  595. });
  596. }
  597.  
  598. // Supported language list
  599. function translateText(lang){
  600. return {
  601. "zh-TW": {
  602. "CLOSE": "關閉",
  603. "IMG": "相片",
  604. "VID": "影片",
  605. "DDL": "快速下載",
  606. "DDL_INTRO": "勾選後將直接下載點選當下位置的相片/影片",
  607. "DW": "下載",
  608. "THUMBNAIL_INTRO": "下載影片縮圖",
  609. "LOAD_BLOB_ONE": "正在載入二進位大型物件...",
  610. "LOAD_BLOB_MULTIPLE": "正在載入多個二進位大型物件...",
  611. "LOAD_BLOB_RELOAD": "正在重新載入二進位大型物件...",
  612. "NO_VID_URL": "找不到影片網址"
  613. },
  614. "zh-CN": {
  615. "CLOSE": "关闭",
  616. "IMG": "图像",
  617. "VID": "视频",
  618. "DDL": "便捷下载",
  619. "DDL_INTRO": "勾选后将直接下载點擊當下位置的图像/视频",
  620. "DW": "下载",
  621. "THUMBNAIL_INTRO": "下载视频缩略图",
  622. "LOAD_BLOB_ONE": "正在载入大型媒体对象...",
  623. "LOAD_BLOB_MULTIPLE": "正在载入多个大型媒体对象...",
  624. "LOAD_BLOB_RELOAD": "正在重新载入大型媒体对象...",
  625. "NO_VID_URL": "找不到视频网址"
  626. },
  627. "en-US": {
  628. "CLOSE": "Close",
  629. "IMG": "Image",
  630. "VID": "Video",
  631. "DDL": "Quick Download",
  632. "DDL_INTRO": "Checking it will direct download current photo/media in the posts.",
  633. "DW": "Download",
  634. "THUMBNAIL_INTRO": "Download video thumbnail.",
  635. "LOAD_BLOB_ONE": "Loading Blob Media...",
  636. "LOAD_BLOB_MULTIPLE": "Loading Blob Media and others...",
  637. "LOAD_BLOB_RELOAD": "Detect Blob Media, now reloading...",
  638. "NO_VID_URL": "Can not find video url."
  639. }
  640. };
  641. }
  642.  
  643. // Translate display text to user country
  644. function _i18n(text){
  645. let userLang = (lang)?lang:"en-US";
  646. let translate = {
  647. "zh-TW": function(){
  648. return translateText()["zh-TW"];
  649. },
  650. "zh-HK": function(){
  651. return translateText()["zh-TW"];
  652. },
  653. "zh-MO": function(){
  654. return translateText()["zh-TW"];
  655. },
  656. "zh-CN": function(){
  657. return translateText()["zh-CN"];
  658. },
  659. "en-US": function(){
  660. return translateText()["en-US"];
  661. }
  662. }
  663.  
  664. try{
  665. return translate[lang]()[text];
  666. }
  667. catch{
  668. return translate["en-US"]()[text];
  669. }
  670. }
  671.  
  672. // Running if document is ready
  673. $(function(){
  674. // Ready~? GO!!
  675. onReadyMyDW();
  676.  
  677. // Close the download dialog if user click the close icon
  678. $('body').on('click','.IG_SN_DIG_BTN,.IG_SN_DIG_BG',function(){
  679. $('.IG_SN_DIG').remove();
  680. });
  681.  
  682. // Hot key [Alt+Q] to close the download dialog
  683. $(window).keydown(function(e){
  684. if (e.keyCode == '81' && e.altKey){
  685. $('.IG_SN_DIG').remove();
  686. e.preventDefault();
  687. }
  688. });
  689. $('body').on('click','.AutoDownload',function(){
  690. if($('.AutoDownload:checked').length){
  691. GM_setValue('AutoDownload',true);
  692. }
  693. else{
  694. GM_setValue('AutoDownload',false);
  695. }
  696. });
  697.  
  698. $('body').on('click','a[data-needed="direct"]',function(){
  699. let date = new Date().getTime();
  700. let timestamp = Math.floor(date / 1000);
  701. saveFiles($(this).attr('data-href'),GL_username,$(this).attr('data-name'),timestamp,$(this).attr('data-type'));
  702. });
  703.  
  704. // Running if user left-click download icon in stories
  705. $('body').on('click','.IG_DWSTORY',function(){
  706. onStoryDW(true);
  707. });
  708.  
  709. // Running if user left-click download icon in stories
  710. $('body').on('click','.IG_DWSTORY_THUMBNAIL',function(){
  711. onStoryThumbnailDW(true);
  712. });
  713.  
  714. $('body').on('click','.IG_DWPROFILE',function(e){
  715. e.stopPropagation();
  716. onProfileDW(true);
  717. });
  718.  
  719. $('body').on('click','.IG_DWHISTORY',function(){
  720. onHighlightsStoryDW(true);
  721. });
  722. $('body').on('click','.IG_DWHISTORY_THUMBNAIL',function(){
  723. onHighlightsStoryThumbnailDW(true);
  724. });
  725. });
  726.  
  727. })(jQuery);