IG小助手

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

当前为 2024-02-11 提交的版本,查看 最新版本

  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.16.4
  9. // @description Downloading is possible for both photos and videos from posts, as well as for stories, reels or profile picture.
  10. // @description:zh-TW 一鍵下載對方 Instagram 貼文中的相片、影片甚至是他們的限時動態、連續短片及大頭貼圖片!
  11. // @description:zh-CN 一键下载对方 Instagram 帖子中的相片、视频甚至是他们的快拍、Reels及头像图片!
  12. // @description:ja 投稿された写真や動画だけでなく、ストーリーズやリール動画からもダウンロードが可能です。
  13. // @description:ko 게시물에 게시된 사진과 동영상 뿐만 아니라 스토리나 릴스에서도 다운로드가 가능합니다.
  14. // @author SN-Koarashi (5026)
  15. // @match https://*.instagram.com/*
  16. // @grant GM_addStyle
  17. // @grant GM_setValue
  18. // @grant GM_getValue
  19. // @grant GM_xmlhttpRequest
  20. // @grant GM_registerMenuCommand
  21. // @grant GM_getResourceText
  22. // @connect i.instagram.com
  23. // @require https://code.jquery.com/jquery-3.6.3.min.js#sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=
  24. // @resource INTERNAL_CSS https://raw.githubusercontent.com/SN-Koarashi/ig-helper/master/style.css
  25. // @supportURL https://github.com/SN-Koarashi/ig-helper/
  26. // @contributionURL https://ko-fi.com/snkoarashi
  27. // @icon https://www.google.com/s2/favicons?domain=www.instagram.com
  28. // @compatible firefox >= 87
  29. // @compatible chrome >= 90
  30. // @compatible edge >= 90
  31. // @license GPL-3.0-only
  32. // ==/UserScript==
  33.  
  34. (function($) {
  35. 'use strict';
  36. // Icon download by https://www.flaticon.com/authors/pixel-perfect
  37.  
  38. /******** USER SETTINGS ********/
  39. // !!! DO NOT CHANGE THIS AREA !!!
  40. // PLEASE CHANGE SETTING WITH MENU
  41. const USER_SETTING = {
  42. 'AUTO_RENAME': (GM_getValue('AUTO_RENAME'))?GM_getValue('AUTO_RENAME'):true,
  43. 'RENAME_SHORTCODE': (GM_getValue('RENAME_SHORTCODE'))?GM_getValue('RENAME_SHORTCODE'):true,
  44. 'DISABLE_VIDEO_LOOPING': (GM_getValue('DISABLE_VIDEO_LOOPING'))?GM_getValue('DISABLE_VIDEO_LOOPING'):false,
  45. 'REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE': (GM_getValue('REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE'))?GM_getValue('REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE'):false,
  46. 'FORCE_FETCH_ALL_RESOURCES': (GM_getValue('FORCE_FETCH_ALL_RESOURCES'))?GM_getValue('FORCE_FETCH_ALL_RESOURCES'):false,
  47. 'DIRECT_DOWNLOAD_WHEN_SINGLE': (GM_getValue('DIRECT_DOWNLOAD_WHEN_SINGLE'))?GM_getValue('DIRECT_DOWNLOAD_WHEN_SINGLE'):false,
  48. 'DIRECT_DOWNLOAD_ALL': (GM_getValue('DIRECT_DOWNLOAD_ALL'))?GM_getValue('DIRECT_DOWNLOAD_ALL'):false,
  49. 'MODIFY_VIDEO_VOLUME': (GM_getValue('MODIFY_VIDEO_VOLUME'))?GM_getValue('MODIFY_VIDEO_VOLUME'):false,
  50. 'SCROLL_BUTTON': (GM_getValue('SCROLL_BUTTON'))?GM_getValue('SCROLL_BUTTON'):true
  51. };
  52. var VIDEO_VOLUME = (GM_getValue('G_VIDEO_VOLUME'))?GM_getValue('G_VIDEO_VOLUME'):1;
  53. /*******************************/
  54.  
  55. const checkInterval = 250;
  56. const lang = navigator.language || navigator.userLanguage;
  57. const style = GM_getResourceText("INTERNAL_CSS");
  58.  
  59. GM_addStyle(style);
  60. GM_registerMenuCommand(_i18n('SETTING'), () => {
  61. showSetting();
  62. });
  63. GM_registerMenuCommand(_i18n('DEBUG'), () => {
  64. showDebugDOM();
  65. });
  66.  
  67. var currentURL = location.href;
  68. var currentHeight = $(document).height();
  69. var firstStarted = false;
  70. var pageLoaded = false;
  71.  
  72. var GL_postPath;
  73. var GL_username;
  74. var GL_repeat;
  75. var GL_dataCache = {
  76. stories: {},
  77. highlights: {}
  78. };
  79.  
  80. // Main Timer
  81. var timer = setInterval(function(){
  82. currentHeight = $(document).height();
  83. // Call Instagram dialog function if url changed.
  84. if(currentURL != location.href || !firstStarted || !pageLoaded){
  85. clearInterval(GL_repeat);
  86. pageLoaded = false;
  87. firstStarted = true;
  88. currentURL = location.href;
  89.  
  90. if(location.href.startsWith("https://www.instagram.com/p/") || location.href.startsWith("https://www.instagram.com/reel/")){
  91. GL_dataCache.stories = {};
  92.  
  93. console.log('isDialog');
  94. setTimeout(()=>{
  95. onReadyMyDW(false);
  96. },150);
  97. pageLoaded = true;
  98. }
  99.  
  100. if(location.href.startsWith("https://www.instagram.com/reels/")){
  101. console.log('isReels');
  102. setTimeout(()=>{
  103. onReels(false);
  104. },150);
  105. pageLoaded = true;
  106. }
  107.  
  108. if(location.href.split("?")[0] == "https://www.instagram.com/"){
  109. GL_dataCache.stories = {};
  110.  
  111. console.log('isHomepage');
  112. setTimeout(()=>{
  113. onReadyMyDW(false);
  114. },150);
  115. pageLoaded = true;
  116. }
  117. if($('div[id^="mount"] > div > div > div').first().is(':hidden') && $('canvas._aarh, div._aadm').length && location.href.match(/^(https:\/\/www\.instagram\.com\/)([0-9A-Za-z\.\-_]+)\/?$/ig) && !location.href.match(/^(https:\/\/www\.instagram\.com\/(stories|explore)\/?)/ig)){
  118. console.log('isProfile');
  119. setTimeout(()=>{
  120. onProfileAvatar(false);
  121. },150);
  122. pageLoaded = true;
  123. }
  124.  
  125. if(!pageLoaded){
  126. // Call Instagram stories function
  127. if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/highlights\/)/ig)){
  128. GL_dataCache.highlights = {};
  129.  
  130. console.log('isHighlightsStory');
  131. onHighlightsStory(false);
  132. GL_repeat = setInterval(()=>{
  133. onHighlightsStoryThumbnail(false);
  134. },checkInterval);
  135.  
  136. if($(".IG_DWHISTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  137. }
  138. else if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/)/ig)){
  139. console.log('isStory');
  140. onStory(false);
  141. onStoryThumbnail(false);
  142.  
  143. if($(".IG_DWSTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  144. }
  145. else{
  146. pageLoaded = false;
  147. // Remove the download icon
  148. $('.IG_DWSTORY').remove();
  149. $('.IG_DWSTORY_THUMBNAIL').remove();
  150. }
  151. }
  152.  
  153. }
  154. },checkInterval);
  155.  
  156. // Call general function when user scroll the page
  157. $(document).scroll(function(){
  158. if(currentHeight != $(this).height() && location.href.split("?")[0] == "https://www.instagram.com/"){
  159. onReadyMyDW();
  160. }
  161. });
  162.  
  163. /**
  164. * onProfileAvatar
  165. * Trigger user avatar download event or button display event.
  166. *
  167. * @param {Boolean} isDownload - Check if it is a download operation
  168. * @return {void}
  169. */
  170. async function onProfileAvatar(isDownload){
  171. if(isDownload){
  172. let date = new Date().getTime();
  173. let timestamp = Math.floor(date / 1000);
  174. let username = location.href.replace(/\/$/ig,'').split('/').at(-1);
  175. let userInfo = await getUserId(username);
  176. try{
  177. let dataURL = await getUserHighSizeProfile(userInfo.user.pk);
  178. saveFiles(dataURL,username,"avatar",timestamp,'jpg');
  179. }
  180. catch(err){
  181. saveFiles(userInfo.user.profile_pic_url,username,"avatar",timestamp,'jpg');
  182. }
  183. }
  184. else{
  185. // Add the profile download button
  186. if(!$('.IG_DWPROFILE').length){
  187. let profileTimer = setInterval(()=>{
  188. if($('.IG_DWPROFILE').length){
  189. clearInterval(profileTimer);
  190. return;
  191. }
  192.  
  193. $('body > div main canvas._aarh, body > div main div._aadm').parent().append(`<div title="${_i18n("DW")}" class="IG_DWPROFILE"><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>`);
  194. $('body > div main canvas._aarh, body > div main div._aadm').parent().css('position','relative');
  195. },150);
  196. }
  197. }
  198. }
  199.  
  200. /**
  201. * onHighlightsStory
  202. * Trigger user's highlight download event or button display event.
  203. *
  204. * @param {Boolean} isDownload - Check if it is a download operation
  205. * @return {void}
  206. */
  207. async function onHighlightsStory(isDownload){
  208. if(isDownload){
  209. let date = new Date().getTime();
  210. let timestamp = Math.floor(date / 1000);
  211. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  212. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length ||
  213. $('body > div section:visible > div > div:not([class]) > div > div div.x1ned7t2.x78zum5 div.x1caxmr6').length ||
  214. $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div').find('div div.x1ned7t2.x78zum5 div.x1caxmr6').length;
  215. let username = "";
  216. let target = 0;
  217.  
  218. if(GL_dataCache.highlights[highlightId]){
  219. console.log('Fetch from memory cache:', highlightId);
  220.  
  221. let totIndex = GL_dataCache.highlights[highlightId].data.reels_media[0].items.length;
  222. username = GL_dataCache.highlights[highlightId].data.reels_media[0].owner.username;
  223. target = GL_dataCache.highlights[highlightId].data.reels_media[0].items[totIndex-nowIndex];
  224. }
  225. else{
  226. let highStories = await getHighlightStories(highlightId);
  227. let totIndex = highStories.data.reels_media[0].items.length;
  228. username = highStories.data.reels_media[0].owner.username;
  229. target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  230.  
  231. GL_dataCache.highlights[highlightId] = highStories;
  232. }
  233.  
  234.  
  235.  
  236. if(target.is_video){
  237. saveFiles(target.video_resources.at(-1).src,username,"highlights",timestamp,'mp4', highlightId);
  238. }
  239. else{
  240. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg', highlightId);
  241. }
  242. }
  243. else{
  244. // Add the stories download button
  245. if(!$('.IG_DWHISTORY').length){
  246. let $element = null;
  247.  
  248. // Default detecter (section layout mode)
  249. if($('body > div section._ac0a').length > 0){
  250. $element = $('body > div section:visible._ac0a');
  251. }
  252. else{
  253. $element = $('body > div section:visible > div > div[style]:not([class])');
  254. $element.css('position','relative');
  255. }
  256.  
  257. // Detecter for div layout mode
  258. // GitHub issue #3: DiceMast3r
  259. if($element.length === 0){
  260. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  261. let nowSize = 0;
  262.  
  263. $$element.each(function(){
  264. if($(this).width() > nowSize){
  265. nowSize = $(this).width();
  266. $element = $(this);
  267. }
  268. });
  269. }
  270.  
  271.  
  272. if($element != null){
  273. //$element.css('position','relative');
  274. $element.append(`<div title="${_i18n("DW")}" class="IG_DWHISTORY"><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>`);
  275. }
  276. }
  277. }
  278. }
  279.  
  280. /**
  281. * onHighlightsStoryThumbnail
  282. * Trigger user's highlight video thumbnail download event or button display event.
  283. *
  284. * @param {Boolean} isDownload - Check if it is a download operation
  285. * @return {void}
  286. */
  287. async function onHighlightsStoryThumbnail(isDownload){
  288. if(isDownload){
  289. let date = new Date().getTime();
  290. let timestamp = Math.floor(date / 1000);
  291. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  292. let username = "";
  293. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length ||
  294. $('body > div section:visible > div > div:not([class]) > div > div div.x1ned7t2.x78zum5 div.x1caxmr6').length ||
  295. $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div').find('div div.x1ned7t2.x78zum5 div.x1caxmr6').length;
  296. let target = "";
  297.  
  298. if(GL_dataCache.highlights[highlightId]){
  299. console.log('Fetch from memory cache:', highlightId);
  300.  
  301. let totIndex = GL_dataCache.highlights[highlightId].data.reels_media[0].items.length;
  302. username = GL_dataCache.highlights[highlightId].data.reels_media[0].owner.username;
  303. target = GL_dataCache.highlights[highlightId].data.reels_media[0].items[totIndex-nowIndex];
  304. }
  305. else{
  306. let highStories = await getHighlightStories(highlightId);
  307. let totIndex = highStories.data.reels_media[0].items.length;
  308. username = highStories.data.reels_media[0].owner.username;
  309. target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  310.  
  311. GL_dataCache.highlights[highlightId] = highStories;
  312. }
  313.  
  314. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg', highlightId);
  315. }
  316. else{
  317. if($('body > div section video.xh8yej3').length){
  318. // Add the stories download button
  319. if(!$('.IG_DWHISTORY_THUMBNAIL').length){
  320. let $element = null;
  321.  
  322. // Default detecter (section layout mode)
  323. if($('body > div section._ac0a').length > 0){
  324. $element = $('body > div section:visible._ac0a');
  325. }
  326. else{
  327. $element = $('body > div section:visible > div > div[style]:not([class])');
  328. $element.css('position','relative');
  329. }
  330.  
  331. // Detecter for div layout mode
  332. // GitHub issue #3: DiceMast3r
  333. if($element.length === 0){
  334. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  335. let nowSize = 0;
  336.  
  337. $$element.each(function(){
  338. if($(this).width() > nowSize){
  339. nowSize = $(this).width();
  340. $element = $(this);
  341. }
  342. });
  343. }
  344.  
  345. if($element != null){
  346. $element.append(`<div title="${_i18n("THUMBNAIL_INTRO")}" class="IG_DWHISTORY_THUMBNAIL"><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>`);
  347. }
  348. }
  349. }
  350. else{
  351. $('.IG_DWHISTORY_THUMBNAIL').remove();
  352. }
  353. }
  354. }
  355.  
  356. /**
  357. * onStory
  358. * Trigger user's story download event or button display event.
  359. *
  360. * @param {Boolean} isDownload - Check if it is a download operation
  361. * @return {void}
  362. */
  363. async function onStory(isDownload,isForce){
  364. if(isDownload){
  365. let date = new Date().getTime();
  366. let timestamp = Math.floor(date / 1000);
  367. let username = $("body > div section._ac0a header._ac0k ._ac0l a + div a").first().text() || location.pathname.split('/').at(2);
  368.  
  369. if($('body > div section:visible video[playsinline]').length > 0){
  370. // Download stories if it is video
  371. let type = "mp4";
  372. let videoURL = "";
  373. let targetURL = location.pathname.replace(/\/$/ig,'').split("/").at(-1);
  374.  
  375. if(GL_dataCache.stories[username] && !isForce){
  376. console.log('Fetch from memory cache:', username);
  377. GL_dataCache.stories[username].data.reels_media[0].items.forEach(item => {
  378. if(item.id == targetURL){
  379. videoURL = item.video_resources[0].src;
  380. }
  381. });
  382.  
  383. if(videoURL.length == 0){
  384. console.log('Memory cache not found, try fetch from API:', username);
  385. onStory(true,true);
  386. return;
  387. }
  388. }
  389. else{
  390. let userInfo = await getUserId(username);
  391. let userId = userInfo.user.pk;
  392. let stories = await getStories(userId);
  393.  
  394. stories.data.reels_media[0].items.forEach(item => {
  395. if(item.id == targetURL){
  396. videoURL = item.video_resources[0].src;
  397. }
  398. });
  399.  
  400. // GitHub issue #4: thinkpad4
  401. if(videoURL.length == 0){
  402. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  403. if($(this).hasClass('x1lix1fw')){
  404. if($(this).children().length > 0){
  405. videoURL = stories.data.reels_media[0].items[index].video_resources[0].src;
  406. }
  407. }
  408. });
  409. }
  410.  
  411. GL_dataCache.stories[username] = stories;
  412. }
  413.  
  414. if(videoURL.length == 0){
  415. alert(_i18n("NO_VID_URL"));
  416. }
  417. else{
  418. saveFiles(videoURL,username,"stories",timestamp,type);
  419. }
  420.  
  421. }
  422. else{
  423. // Download stories if it is image
  424. let srcset = $('body > div section:visible img[referrerpolicy][class], body > div section:visible img[crossorigin][class]:not([alt])').attr('srcset')?.split(',')[0]?.split(' ')[0];
  425. let link = (srcset)?srcset:$('body > div section:visible img[referrerpolicy][class], body > div section:visible img[crossorigin][class]:not([alt])').attr('src');
  426.  
  427. if(!link){
  428. // _aa63 mean stories picture in stories page (not avatar)
  429. let $element = $('body > div section:visible img._aa63');
  430. link = ($element.attr('srcset'))?$element.attr('srcset')?.split(',')[0]?.split(' ')[0]:$element.attr('src');
  431. }
  432.  
  433. let downloadLink = link;
  434. let type = 'jpg';
  435.  
  436. saveFiles(downloadLink,username,"stories",timestamp,type);
  437. }
  438. }
  439. else{
  440. // Add the stories download button
  441. let style = "position: absolute;right:-40px;top:15px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  442. if(!$('.IG_DWSTORY').length){
  443. GL_dataCache.stories = {};
  444. let $element = null;
  445. // Default detecter (section layout mode)
  446. if($('body > div section._ac0a').length > 0){
  447. $element = $('body > div section:visible._ac0a');
  448. }
  449. // detecter (single story layout mode)
  450. else{
  451. $element = $('body > div section:visible > div > div[style]:not([class])');
  452. $element.css('position','relative');
  453. }
  454.  
  455.  
  456. // Detecter for div layout mode
  457. // GitHub issue #3: DiceMast3r
  458. if($element.length === 0){
  459. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  460. let nowSize = 0;
  461.  
  462. $$element.each(function(){
  463. if($(this).width() > nowSize){
  464. nowSize = $(this).width();
  465. $element = $(this);
  466. }
  467. });
  468. }
  469.  
  470.  
  471. if($element != null){
  472. $element.css('position','relative');
  473. $element.append(`<div title="${_i18n("DW")}" class="IG_DWSTORY"><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>`);
  474. }
  475. }
  476. }
  477. }
  478.  
  479. /**
  480. * onStoryThumbnail
  481. * Trigger user's story video thumbnail download event or button display event.
  482. *
  483. * @param {Boolean} isDownload - Check if it is a download operation
  484. * @return {void}
  485. */
  486. async function onStoryThumbnail(isDownload,isForce){
  487. if(isDownload){
  488. // Download stories if it is video
  489. let date = new Date().getTime();
  490. let timestamp = Math.floor(date / 1000);
  491. let type = 'jpg';
  492. let username = $("body > div section._ac0a header._ac0k ._ac0l a + div a").first().text() || location.pathname.split('/').at(2);
  493. 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;';
  494. // Download thumbnail
  495. let targetURL = location.pathname.replace(/\/$/ig,'').split("/").at(-1);
  496. let videoThumbnailURL = "";
  497.  
  498.  
  499. if(GL_dataCache.stories[username] && !isForce){
  500. console.log('Fetch from memory cache:', username);
  501. GL_dataCache.stories[username].data.reels_media[0].items.forEach(item => {
  502. if(item.id == targetURL){
  503. videoThumbnailURL = item.display_url;
  504. }
  505. });
  506.  
  507. if(videoThumbnailURL.length == 0){
  508. console.log('Memory cache not found, try fetch from API:', username);
  509. onStoryThumbnail(true,true);
  510. return;
  511. }
  512. }
  513. else{
  514. let userInfo = await getUserId(username);
  515. let userId = userInfo.user.pk;
  516. let stories = await getStories(userId);
  517.  
  518. stories.data.reels_media[0].items.forEach(item => {
  519. if(item.id == targetURL){
  520. videoThumbnailURL = item.display_url;
  521. }
  522. });
  523.  
  524. // GitHub issue #4: thinkpad4
  525. if(videoThumbnailURL.length == 0){
  526. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  527. if($(this).hasClass('x1lix1fw')){
  528. if($(this).children().length > 0){
  529. videoThumbnailURL = stories.data.reels_media[0].items[index].display_url;
  530. }
  531. }
  532. });
  533. }
  534. }
  535.  
  536. saveFiles(videoThumbnailURL,username,"thumbnail",timestamp,type);
  537. }
  538. else{
  539. if($('body > div section video.xh8yej3').length){
  540. // Add the stories download button
  541. if(!$('.IG_DWSTORY_THUMBNAIL').length){
  542. let $element = null;
  543. // Default detecter (section layout mode)
  544. if($('body > div section._ac0a').length > 0){
  545. $element = $('body > div section:visible._ac0a');
  546. }
  547. // detecter (single story layout mode)
  548. else{
  549. $element = $('body > div section:visible > div > div[style]:not([class])');
  550. $element.css('position','relative');
  551. }
  552.  
  553. // Detecter for div layout mode
  554. // GitHub issue #3: DiceMast3r
  555. if($element.length === 0){
  556. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  557. let nowSize = 0;
  558.  
  559. $$element.each(function(){
  560. if($(this).width() > nowSize){
  561. nowSize = $(this).width();
  562. $element = $(this);
  563. }
  564. });
  565. }
  566.  
  567.  
  568. if($element != null){
  569. $element.css('position','relative');
  570. $element.append(`<div title="${_i18n("THUMBNAIL_INTRO")}" class="IG_DWSTORY_THUMBNAIL"><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>`);
  571. }
  572. }
  573. }
  574. else{
  575. $('.IG_DWSTORY_THUMBNAIL').remove();
  576. }
  577. }
  578. }
  579.  
  580. /**
  581. * onReels
  582. * Trigger user's reels download event or button display event.
  583. *
  584. * @param {Boolean} isDownload - Check if it is a download operation
  585. * @param {Boolean} isVideo - Check if reel is a video element
  586. * @return {void}
  587. */
  588. async function onReels(isDownload, isVideo){
  589. if(isDownload){
  590. let reelsPath = location.href.split('?').at(0).split('instagram.com/reels/').at(-1).replaceAll('/','');
  591. let data = await getBlobMedia(reelsPath);
  592. let timestamp = new Date().getTime();
  593.  
  594. if(isVideo && data.shortcode_media.is_video){
  595. let type = 'mp4';
  596. saveFiles(data.shortcode_media.video_url,data.shortcode_media.owner.username,"reels",timestamp,type,reelsPath);
  597. }
  598. else{
  599. let type = 'jpg';
  600. saveFiles(data.shortcode_media.display_resources.at(-1).src,data.shortcode_media.owner.username,"reels",timestamp,type,reelsPath);
  601. }
  602. }
  603. else{
  604. //$('.IG_REELS_THUMBNAIL, .IG_REELS').remove();
  605. var timer = setInterval(()=>{
  606. if($('section > main[role="main"] > div div.x1qjc9v5 video').length > 0){
  607. clearInterval(timer);
  608.  
  609. if(USER_SETTING.SCROLL_BUTTON){
  610. $('#scrollWrapper').remove();
  611. $('section > main[role="main"]').append('<section id="scrollWrapper"></section>');
  612. $('section > main[role="main"] > #scrollWrapper').append('<div class="button-up"><div></div></div>');
  613. $('section > main[role="main"] > #scrollWrapper').append('<div class="button-down"><div></div></div>');
  614.  
  615. $('section > main[role="main"] > #scrollWrapper > .button-up').on('click',function(){
  616. $('section > main[role="main"] > div')[0].scrollBy({top: -1, behavior: "smooth"});
  617. });
  618. $('section > main[role="main"] > #scrollWrapper > .button-down').on('click',function(){
  619. $('section > main[role="main"] > div')[0].scrollBy({top: 1, behavior: "smooth"});
  620. });
  621. }
  622.  
  623. $('section > main[role="main"] > div').children('div').each(function(){
  624. if($(this).children().length > 0){
  625. if(!$(this).children().find('.IG_REELS').length){
  626. $(this).children().css('position','relative');
  627.  
  628. $(this).children().append(`<div title="${_i18n("DW")}" class="IG_REELS"><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>`);
  629. $(this).children().append(`<div title="${_i18n("THUMBNAIL_INTRO")}" class="IG_REELS_THUMBNAIL"><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>`);
  630.  
  631. // Disable video autoplay
  632. if(USER_SETTING.DISABLE_VIDEO_LOOPING){
  633. $(this).find('video').each(function(){
  634. if(!$(this).data('loop')){
  635. console.log('(reel) Added video event listener #loop');
  636. $(this).on('ended',function(){
  637. $(this).attr('data-loop', true);
  638. let $element = $(this).next().find('div[role="presentation"] > div > div:last-child');
  639.  
  640. if($element.length > 0){
  641. $element.click();
  642. console.log('paused click()');
  643. }
  644. else{
  645. $(this).parent().find('.xpgaw4o').removeAttr('style');
  646. this.pause();
  647. console.log('paused pause()');
  648. }
  649. });
  650. }
  651. });
  652. }
  653. // Modify Video Volume
  654. if(USER_SETTING.MODIFY_VIDEO_VOLUME){
  655. $(this).find('video').each(function(){
  656. if(!$(this).data('modify')){
  657. console.log('(reel) Added video event listener #modify');
  658. this.volume = VIDEO_VOLUME;
  659.  
  660. $(this).on('play',function(){
  661. this.volume = VIDEO_VOLUME;
  662. });
  663. $(this).on('playing',function(){
  664. this.volume = VIDEO_VOLUME;
  665. });
  666.  
  667. $(this).attr('data-modify', true);
  668. }
  669. });
  670. }
  671. }
  672. }
  673. });
  674. }
  675. },250);
  676. }
  677. }
  678.  
  679. /**
  680. * getHighlightStories
  681. * Get a list of all stories in highlight Id.
  682. *
  683. * @param {Integer} highlightId
  684. * @return {Object}
  685. */
  686. function getHighlightStories(highlightId){
  687. return new Promise((resolve,reject)=>{
  688. 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`;
  689.  
  690. GM_xmlhttpRequest({
  691. method: "GET",
  692. url: getURL,
  693. onload: function(response) {
  694. let obj = JSON.parse(response.response);
  695. resolve(obj);
  696. },
  697. onerror: function(err){
  698. reject(err);
  699. }
  700. });
  701. });
  702. }
  703.  
  704. /**
  705. * getStories
  706. * Get a list of all stories in user Id.
  707. *
  708. * @param {Integer} userId
  709. * @return {Object}
  710. */
  711. function getStories(userId){
  712. return new Promise((resolve,reject)=>{
  713. 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`;
  714.  
  715. GM_xmlhttpRequest({
  716. method: "GET",
  717. url: getURL,
  718. onload: function(response) {
  719. let obj = JSON.parse(response.response);
  720. resolve(obj);
  721. },
  722. onerror: function(err){
  723. reject(err);
  724. }
  725. });
  726. });
  727. }
  728.  
  729. /**
  730. * getUserId
  731. * Get user's id with username
  732. *
  733. * @param {String} username
  734. * @return {Integer}
  735. */
  736. function getUserId(username){
  737. return new Promise((resolve,reject)=>{
  738. let getURL = `https://www.instagram.com/web/search/topsearch/?query=${username}`;
  739.  
  740. GM_xmlhttpRequest({
  741. method: "GET",
  742. url: getURL,
  743. onload: function(response) {
  744. let obj = JSON.parse(response.response);
  745. resolve(obj.users[0]);
  746. },
  747. onerror: function(err){
  748. reject(err);
  749. }
  750. });
  751. });
  752. }
  753.  
  754. /**
  755. * getUserHighSizeProfile
  756. * Get user's high quality avatar image.
  757. *
  758. * @param {Integer} userId
  759. * @return {String}
  760. */
  761. function getUserHighSizeProfile(userId){
  762. return new Promise((resolve,reject)=>{
  763. let getURL = `https://i.instagram.com/api/v1/users/${userId}/info/`;
  764.  
  765. GM_xmlhttpRequest({
  766. method: "GET",
  767. url: getURL,
  768. headers: {
  769. 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; Pixel 7 XL)Build/RP1A.20845.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0 Chrome/117.0.5938.60 Mobile Safari/537.36 Instagram 307.0.0.34.111'
  770. },
  771. onload: function(response) {
  772. let obj = JSON.parse(response.response);
  773. if(obj.status !== 'ok'){
  774. reject('faild');
  775. }
  776. else{
  777. resolve(obj.user.hd_profile_pic_url_info?.url);
  778. }
  779. },
  780. onerror: function(err){
  781. reject(err);
  782. }
  783. });
  784. });
  785. }
  786.  
  787. /**
  788. * getPostOwner
  789. * Get post's author with post shortcode
  790. *
  791. * @param {String} postPath
  792. * @return {String}
  793. */
  794. function getPostOwner(postPath){
  795. return new Promise((resolve,reject)=>{
  796. if(!postPath) reject("NOPATH");
  797. let postShortCode = postPath;
  798. let getURL = `https://www.instagram.com/graphql/query/?query_hash=2c4c2e343a8f64c625ba02b2aa12c7f8&variables=%7B%22shortcode%22:%22${postShortCode}%22}`;
  799.  
  800. GM_xmlhttpRequest({
  801. method: "GET",
  802. url: getURL,
  803. onload: function(response) {
  804. let obj = JSON.parse(response.response);
  805. resolve(obj.data.shortcode_media.owner.username);
  806. },
  807. onerror: function(err){
  808. reject(err);
  809. }
  810. });
  811. });
  812. }
  813.  
  814. /**
  815. * getBlobMedia
  816. * Get list of all media files in post with post shortcode
  817. *
  818. * @param {String} postPath
  819. * @return {Object}
  820. */
  821. function getBlobMedia(postPath){
  822. return new Promise((resolve,reject)=>{
  823. if(!postPath) reject("NOPATH");
  824. let postShortCode = postPath;
  825. let getURL = `https://www.instagram.com/graphql/query/?query_hash=2c4c2e343a8f64c625ba02b2aa12c7f8&variables=%7B%22shortcode%22:%22${postShortCode}%22}`;
  826.  
  827. GM_xmlhttpRequest({
  828. method: "GET",
  829. url: getURL,
  830. onload: function(response) {
  831. let obj = JSON.parse(response.response);
  832. console.log(obj);
  833. resolve(obj.data);
  834. },
  835. onerror: function(err){
  836. reject(err);
  837. }
  838. });
  839. });
  840. }
  841.  
  842. /**
  843. * onReadyMyDW
  844. * Create an event entry point for the download button for the post
  845. *
  846. * @param {Boolean} NoDialog - Check if it not showing the dialog
  847. * @return {void}
  848. */
  849. function onReadyMyDW(NoDialog){
  850. // Whether is Instagram dialog?
  851. if(!NoDialog){
  852. // Running if it is dialog
  853. $('article, main > div > div.xdt5ytf > div').each(function(){
  854. $(this).removeAttr('data-snig');
  855. $(this).unbind('click');
  856. });
  857. $('.SNKMS_IG_DW_MAIN,.SNKMS_IG_DW_MAIN_VIDEO').remove();
  858. }
  859. if(NoDialog == false){
  860. var repeat = setInterval(() => {
  861. // div.xdt5ytf << (sigle post in top, not floating) >>
  862. if($('article[data-snig="canDownload"], section:visible > main > div > div.xdt5ytf[data-snig="canDownload"], div[id^="mount"] > div > div > div.x1n2onr6.x1vjfegm div[data-snig="canDownload"]').length > 0) clearInterval(repeat);
  863. createDownloadButton();
  864. },250);
  865. }
  866. else{
  867. createDownloadButton();
  868. }
  869. }
  870.  
  871. /**
  872. * createDownloadButton
  873. * Create a download button in the upper right corner of each post
  874. *
  875. * @return {void}
  876. */
  877. function createDownloadButton(){
  878. // Add download icon per each posts
  879. $('article, section:visible > main > div > div.xdt5ytf, div._aap0[role="presentation"]').each(function(index){
  880. // If it is have not download icon
  881. // class x1iyjqo2 mean user profile pages post list container
  882. if(!$(this).attr('data-snig') && !$(this).hasClass('x1iyjqo2') && !$(this).children('article')?.hasClass('x1iyjqo2')){
  883. console.log("Found article");
  884. var rightPos = 15;
  885. var topPos = 15;
  886. var $mainElement = $(this);
  887.  
  888. // not loop each in single top post
  889. if(this.tagName === "DIV" && index != 0){
  890. return;
  891. }
  892.  
  893. // New post UI by Discord: ken
  894. if(this.tagName === "DIV" && $(this).attr('role') === "presentation"){
  895. rightPos = 28;
  896. topPos = 75;
  897. $mainElement = $('div._aap0[role="presentation"]').parents('div._aamm').parent().parent().parent().parent().parent();
  898. }
  899.  
  900.  
  901. // Add the download icon
  902. let $childElement = $mainElement.children("div").children("div");
  903. $childElement.eq((this.tagName === "DIV")? 0 : $childElement.length - 2).append(`<div title="${_i18n("DW")}" class="SNKMS_IG_DW_MAIN" style="right:${rightPos}px;top:${topPos}px;"><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>`);
  904. $childElement.css('position','relative');
  905.  
  906. // Disable video autoplay
  907. if(USER_SETTING.DISABLE_VIDEO_LOOPING){
  908. $(this).find('video').each(function(){
  909. if(!$(this).data('loop')){
  910. console.log('(post) Added video event listener #loop');
  911. $(this).on('ended',function(){
  912. $(this).attr('data-loop', true);
  913. this.pause();
  914. });
  915. }
  916. });
  917. }
  918.  
  919. // Modify Video Volume
  920. if(USER_SETTING.MODIFY_VIDEO_VOLUME){
  921. $(this).find('video').each(function(){
  922. if(!$(this).data('modify')){
  923. console.log('(post) Added video event listener #modify');
  924. this.volume = VIDEO_VOLUME;
  925.  
  926. $(this).on('play',function(){
  927. this.volume = VIDEO_VOLUME;
  928. });
  929. $(this).on('playing',function(){
  930. this.volume = VIDEO_VOLUME;
  931. });
  932.  
  933. $(this).attr('data-modify', true);
  934. }
  935. });
  936. }
  937.  
  938. // Running if user click the download icon
  939. $(this).on('click','.SNKMS_IG_DW_MAIN', async function(e){
  940. GL_username = $(this).parent().parent().parent().attr('data-username');
  941. GL_postPath = location.pathname.replace(/\/$/,'').split('/').at(-1) || $(this).parent().parent().children("div:last-child").children("div").children("div:last-child").find('a[href^="/p/"]').last().attr("href").split("/").at(2);
  942.  
  943. // Create element that download dailog
  944. IG_createDM(USER_SETTING.DIRECT_DOWNLOAD_ALL, true);
  945.  
  946. $("#article-id").html(`<a href="https://www.instagram.com/p/${GL_postPath}">${GL_postPath}</a>`);
  947.  
  948. if(!USER_SETTING.DIRECT_DOWNLOAD_ALL){
  949. // Find video/image element and add the download icon
  950. var s = 0;
  951. var multiple = $(this).parent().parent().find('._aap0 ._acaz').length;
  952. var pathname = window.location.pathname;
  953. var fullpathname = "/"+pathname.split('/')[1]+"/"+pathname.split('/')[2]+"/";
  954. var blob = USER_SETTING.FORCE_FETCH_ALL_RESOURCES;
  955.  
  956. // If posts have more than one images or videos.
  957. if(multiple){
  958. $(this).parent().find('._aap0 ._acaz').each(function(){
  959. let element_videos = $(this).parent().parent().find('video');
  960. //if(element_videos && element_videos.attr('src') && element_videos.attr('src').match(/^blob:/ig)){
  961. if(element_videos && element_videos.attr('src')){
  962. blob = true;
  963. }
  964. });
  965.  
  966.  
  967. if(blob){
  968. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_MULTIPLE"));
  969. }
  970. else{
  971. $(this).parent().find('._aap0 ._acaz').each(function(){
  972. s++;
  973. let element_videos = $(this).find('video');
  974. let element_images = $(this).find('._aagv img');
  975. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  976.  
  977. if(element_videos && element_videos.attr('src')){
  978. blob = true;
  979. }
  980. if(element_images && imgLink){
  981. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-path="${GL_postPath}" data-name="photo" data-type="jpg" data-globalIndex="${s}" href="javascript:;" data-href="${imgLink}"><img width="100" src="${imgLink}" /><br/>- ${_i18n("IMG")} ${s} -</a>`);
  982. }
  983.  
  984. });
  985.  
  986. if(blob){
  987. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_RELOAD"));
  988. }
  989. }
  990. }
  991. else{
  992. s++;
  993. let element_videos = $(this).parent().parent().find('video');
  994. let element_images = $(this).parent().parent().find('._aagv img');
  995. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  996.  
  997.  
  998. if(element_videos && element_videos.attr('src')){
  999. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_ONE"));
  1000. }
  1001. if(element_images && imgLink){
  1002. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY').append(`<a data-needed="direct" data-path="${GL_postPath}" data-name="photo" data-type="jpg" data-globalIndex="${s}" href="javascript:;" href="" data-href="${imgLink}"><img width="100" src="${imgLink}" /><br/>- ${_i18n("IMG")} ${s} -</a>`);
  1003. }
  1004. }
  1005. }
  1006.  
  1007. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1008. $(this).wrap('<div></div>');
  1009. $(this).before('<label class="inner_box_wrapper"><input class="inner_box" type="checkbox"><span></span></label>');
  1010. });
  1011.  
  1012. if(USER_SETTING.DIRECT_DOWNLOAD_ALL){
  1013. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_MULTIPLE"));
  1014. let checkBlob = setInterval(()=>{
  1015. if($('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').length > 0){
  1016. clearInterval(checkBlob);
  1017. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1018. $(this).click();
  1019. });
  1020.  
  1021. $('.IG_SN_DIG').remove();
  1022. }
  1023. },250);
  1024. }
  1025. else if(s === 1 && USER_SETTING.DIRECT_DOWNLOAD_WHEN_SINGLE){
  1026. IG_setDM(true);
  1027. let checkBlob = setInterval(()=>{
  1028. if($('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').length > 0){
  1029. clearInterval(checkBlob);
  1030. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').first()?.click();
  1031. $('.IG_SN_DIG').remove();
  1032. }
  1033. },250);
  1034. }
  1035. });
  1036.  
  1037. // Add the mark that download is ready
  1038. var username = $(this).find("header > div:last-child > div:first-child span a").first().text();
  1039.  
  1040. $(this).attr('data-snig','canDownload');
  1041. $(this).attr('data-username',username);
  1042. }
  1043. });
  1044. }
  1045.  
  1046. /**
  1047. * createMediaListDOM
  1048. * Create a list of media elements from post URLs
  1049. *
  1050. * @param {String} postURL
  1051. * @param {String} selector - Use CSS element selectors to choose where it appears.
  1052. * @param {String} message - i18n display loading message
  1053. * @return {void}
  1054. */
  1055. async function createMediaListDOM(postURL,selector,message){
  1056. $(`${selector} a`).remove();
  1057. $(selector).append('<p id="_SNLOAD">'+ message +'</p>');
  1058. let media = await getBlobMedia(postURL);
  1059. let idx = 1;
  1060. let resource = media.shortcode_media;
  1061.  
  1062. // GraphVideo
  1063. if(resource.__typename == "GraphVideo" && resource.video_url){
  1064. $(selector).append(`<a data-blob="true" data-needed="direct" data-path="${resource.shortcode}" data-name="video" data-type="mp4" data-username="${resource.owner.username}" data-globalIndex="${idx}" href="javascript:;" data-href="${resource.video_url}"><img width="100" src="${resource.display_resources[1].src}" /><br/>- ${_i18n("VID")} ${idx} -</a>`);
  1065. idx++;
  1066. }
  1067. // GraphImage
  1068. if(resource.__typename == "GraphImage"){
  1069. $(selector).append(`<a data-blob="true" data-needed="direct" data-path="${resource.shortcode}" data-name="photo" data-type="jpg" data-username="${resource.owner.username}" data-globalIndex="${idx}" href="javascript:;" data-href="${resource.display_resources[resource.display_resources.length - 1].src}"><img width="100" src="${resource.display_resources[1].src}" /><br/>- ${_i18n("IMG")} ${idx} -</a>`);
  1070. idx++;
  1071. }
  1072. // GraphSidecar
  1073. if(resource.__typename == "GraphSidecar" && resource.edge_sidecar_to_children){
  1074. for(let e of resource.edge_sidecar_to_children.edges){
  1075. if(e.node.__typename == "GraphVideo"){
  1076. $(selector).append(`<a data-blob="true" data-needed="direct" data-path="${resource.shortcode}" data-name="video" data-type="mp4" data-username="${resource.owner.username}" data-globalIndex="${idx}" href="javascript:;" data-href="${e.node.video_url}"><img width="100" src="${e.node.display_resources[1].src}" /><br/>- ${_i18n("VID")} ${idx} -</a>`);
  1077. }
  1078.  
  1079. if(e.node.__typename == "GraphImage"){
  1080. $(selector).append(`<a data-blob="true" data-needed="direct" data-path="${resource.shortcode}" data-name="photo" data-type="jpg" data-username="${resource.owner.username}" data-globalIndex="${idx}" 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>`);
  1081. }
  1082. idx++;
  1083. }
  1084. }
  1085.  
  1086. $("#_SNLOAD").remove();
  1087. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1088. $(this).wrap('<div></div>');
  1089. $(this).before('<label class="inner_box_wrapper"><input class="inner_box" type="checkbox"><span></span></label>');
  1090. });
  1091. }
  1092.  
  1093. /**
  1094. * IG_createDM
  1095. * A dialog showing a list of all media files in the post
  1096. *
  1097. * @param {Boolean} hasHidden
  1098. * @param {Boolean} hasCheckbox
  1099. * @return {void}
  1100. */
  1101. function IG_createDM(hasHidden, hasCheckbox){
  1102. let isHidden = (hasHidden)?"hidden":"";
  1103. $('body').append('<div class="IG_SN_DIG '+isHidden+'"><div class="IG_SN_DIG_BG"></div><div class="IG_SN_DIG_MAIN"><div class="IG_SN_DIG_TITLE"></div><div class="IG_SN_DIG_BODY"></div></div></div>');
  1104. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE').append('<div style="position:relative;height:36px;text-align:center;margin-bottom: 7px;"><div style="position:absolute;left:0px;line-height: 18px;"><kbd>Alt</kbd>+<kbd>Q</kbd> ['+_i18n("CLOSE")+']</div><div style="line-height: 18px;">IG Helper</div><div id="post_info" style="line-height: 14px;font-size:14px;">Post ID: <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;fill: rgb(var(--ig-primary-text));" 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>');
  1105.  
  1106. if(hasCheckbox){
  1107. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE').append(`<div style="text-align: center;" id="button_group"></div>`);
  1108. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE > div#button_group').append(`<button id="batch_download_selected">${_i18n('BATCH_DOWNLOAD_SELECTED')}</button>`);
  1109. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE > div#button_group').append(`<button id="batch_download_direct">${_i18n('BATCH_DOWNLOAD_DIRECT')}</button>`);
  1110. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE').append(`<br/><label class="checkbox"><input value="yes" type="checkbox" />${_i18n('ALL_CHECK')}</label>`);
  1111. }
  1112. }
  1113.  
  1114. /**
  1115. * IG_setDM
  1116. * Set a dialog status
  1117. *
  1118. * @param {Boolean} hasHidden
  1119. * @return {void}
  1120. */
  1121. function IG_setDM(hasHidden){
  1122. if($('.IG_SN_DIG').length){
  1123. if(hasHidden){
  1124. $('.IG_SN_DIG').addClass("hidden");
  1125. }
  1126. else{
  1127. $('.IG_SN_DIG').removeClass("hidden");
  1128. }
  1129. }
  1130. }
  1131.  
  1132. /**
  1133. * saveFiles
  1134. * Download the specified media URL to the computer
  1135. *
  1136. * @param {String} downloadLink
  1137. * @param {String} username
  1138. * @param {String} sourceType
  1139. * @param {Integer} timestamp
  1140. * @param {String} filetype
  1141. * @param {String} shortcode
  1142. * @return {void}
  1143. */
  1144. function saveFiles(downloadLink,username,sourceType,timestamp,filetype,shortcode){
  1145. $('div[id^="mount"] > div > div > div:first').removeClass('x1s85apg');
  1146. $('div[id^="mount"] > div > div > div:first').css('z-index','20000');
  1147. fetch(downloadLink).then(res => {
  1148. return res.blob().then(dwel => {
  1149. $('div[id^="mount"] > div > div > div:first').addClass('x1s85apg');
  1150. $('div[id^="mount"] > div > div > div:first').css('z-index','');
  1151. createSaveFileElement(downloadLink,dwel,username,sourceType,timestamp,filetype,shortcode);
  1152. });
  1153. });
  1154. }
  1155.  
  1156. /**
  1157. * createSaveFileElement
  1158. * Download the specified media with link element
  1159. *
  1160. * @param {String} downloadLink
  1161. * @param {Object} object
  1162. * @param {String} username
  1163. * @param {String} sourceType
  1164. * @param {Integer} timestamp
  1165. * @param {String} filetype
  1166. * @param {String} shortcode
  1167. * @return {void}
  1168. */
  1169. function createSaveFileElement(downloadLink,object,username,sourceType,timestamp,filetype,shortcode) {
  1170. const a = document.createElement("a");
  1171. const name = username+'-'+sourceType+'-'+((USER_SETTING.RENAME_SHORTCODE && shortcode)?shortcode+'-':'')+timestamp+'.'+filetype;
  1172. const originally = username + '_' + downloadLink.split('/').at(-1).split('?').at(0);
  1173.  
  1174. a.href = URL.createObjectURL(object);
  1175. a.setAttribute("download", (USER_SETTING.AUTO_RENAME)?name:originally);
  1176. a.click();
  1177. a.remove();
  1178. }
  1179.  
  1180. /**
  1181. * triggerLinkElement
  1182. * Trigger the link element to start downloading the resource
  1183. *
  1184. * @param {Object} element
  1185. * @return {void}
  1186. */
  1187. async function triggerLinkElement(element) {
  1188. let date = new Date().getTime();
  1189. let timestamp = Math.floor(date / 1000);
  1190. let username = ($(element).attr('data-username')) ? $(element).attr('data-username') : GL_username;
  1191.  
  1192. if(!username && $(element).attr('data-path')){
  1193. console.log('catching owner name from shortcode:',$(element).attr('data-href'));
  1194. username = await getPostOwner($(element).attr('data-path'));
  1195. }
  1196.  
  1197.  
  1198. saveFiles($(element).attr('data-href'),username,$(element).attr('data-name'),timestamp,$(element).attr('data-type'), $(element).attr('data-path'));
  1199. }
  1200.  
  1201. /**
  1202. * translateText
  1203. * i18n translation text
  1204. *
  1205. * @param {String} lang
  1206. * @return {void}
  1207. */
  1208. function translateText(lang){
  1209. return {
  1210. "zh-TW": {
  1211. "SHOW_DOM_TREE": "顯示 DOM Tree",
  1212. "SELECT_AND_COPY": "全選並複製輸入框的內容",
  1213. "DOWNLOAD_DOM_TREE": "將 DOM Tree 下載為文字文件",
  1214. "REPORT_GITHUB": "在 GitHub 上回報問題",
  1215. "REPORT_DISCORD": "在 Discord 支援伺服器上回報問題",
  1216. "DEBUG": "偵錯視窗",
  1217. "CLOSE": "關閉",
  1218. "ALL_CHECK": "全選",
  1219. "BATCH_DOWNLOAD_SELECTED": "批次下載已勾選資源",
  1220. "BATCH_DOWNLOAD_DIRECT": "批次下載全部資源",
  1221. "IMG": "相片",
  1222. "VID": "影片",
  1223. "DDL": "快速下載",
  1224. "DDL_INTRO": "勾選後將直接下載點選當下位置的相片/影片",
  1225. "DW": "下載",
  1226. "THUMBNAIL_INTRO": "下載影片縮圖",
  1227. "LOAD_BLOB_ONE": "正在載入二進位大型物件...",
  1228. "LOAD_BLOB_MULTIPLE": "正在載入多個二進位大型物件...",
  1229. "LOAD_BLOB_RELOAD": "正在重新載入二進位大型物件...",
  1230. "NO_CHECK_RESOURCE": "您需要勾選資源才能下載。",
  1231. "NO_VID_URL": "找不到影片網址",
  1232. "SETTING": "設定",
  1233. "AUTO_RENAME": "自動重新命名檔案",
  1234. "RENAME_SHORTCODE": "重新命名檔案並包含 Shortcode",
  1235. "DISABLE_VIDEO_LOOPING": "關閉影片自動循環播放",
  1236. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "右鍵點擊使用者限時動態區域頭貼時重定向",
  1237. "FORCE_FETCH_ALL_RESOURCES": "強制提取貼文中所有資源",
  1238. "DIRECT_DOWNLOAD_WHEN_SINGLE": "直接下載貼文中的單一資源",
  1239. "DIRECT_DOWNLOAD_ALL": "直接下載貼文中的所有資源",
  1240. "MODIFY_VIDEO_VOLUME": "修改影片音量(右鍵設定)",
  1241. "SCROLL_BUTTON": "為連續短片頁面啟用捲動按鈕",
  1242. "AUTO_RENAME_INTRO": "將檔案自動重新命名為以下格式:\n使用者名稱-類型-時間戳.檔案類型\n例如:instagram-photo-1670350000.jpg\n\n若設為 false,則檔案名稱將保持原始樣貌。 \n例如:instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1243. "RENAME_SHORTCODE_INTRO": "將檔案自動重新命名為以下格式:\n使用者名稱-類型-Shortcode-時間戳.檔案類型\n示例:instagram-photo-CwkxyiVynpW-1670350000.jpg\n\n此功能僅在[自動重新命名檔案]設定為 TRUE 時有效。",
  1244. "DISABLE_VIDEO_LOOPING_INTRO": "關閉連續短片和貼文中影片自動循環播放。",
  1245. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "右鍵點選首頁限時動態區域中的使用者頭貼時,重新導向到使用者的個人資料頁面。",
  1246. "FORCE_FETCH_ALL_RESOURCES_INTRO": "透過 Instagram API 強制取得貼文中的所有資源(照片和影片),以取消每個貼文單次提取三個資源的限制。",
  1247. "DIRECT_DOWNLOAD_WHEN_SINGLE_INTRO": "當貼文僅有單一資源時直接下載。",
  1248. "DIRECT_DOWNLOAD_ALL_INTRO": "按下下載按鈕時將直接強制提取貼文中的所有資源並下載。",
  1249. "MODIFY_VIDEO_VOLUME_INTRO": "修改連續短片和貼文的影片播放音量(右鍵可開啟音量設定條)。",
  1250. "SCROLL_BUTTON_INTRO": "為連續短片頁面的右下角啟用上下捲動按鈕。"
  1251. },
  1252. "zh-CN": {
  1253. "SHOW_DOM_TREE": "显示 DOM Tree",
  1254. "SELECT_AND_COPY": "全选并复制输入框的内容",
  1255. "DOWNLOAD_DOM_TREE": "将 DOM Tree 下载为文本文件",
  1256. "REPORT_GITHUB": "在 GitHub 上报告问题",
  1257. "REPORT_DISCORD": "在 Discord 支援服务器上报告问题",
  1258. "DEBUG": "调试窗口",
  1259. "CLOSE": "关闭",
  1260. "ALL_CHECK": "全选",
  1261. "BATCH_DOWNLOAD_SELECTED": "批量下载已勾选资源",
  1262. "BATCH_DOWNLOAD_DIRECT": "批量下载全部资源",
  1263. "IMG": "图像",
  1264. "VID": "视频",
  1265. "DDL": "便捷下载",
  1266. "DDL_INTRO": "勾选后将直接下载點擊當下位置的图像/视频",
  1267. "DW": "下载",
  1268. "THUMBNAIL_INTRO": "下载视频缩略图",
  1269. "LOAD_BLOB_ONE": "正在载入大型媒体对象...",
  1270. "LOAD_BLOB_MULTIPLE": "正在载入多个大型媒体对象...",
  1271. "LOAD_BLOB_RELOAD": "正在重新载入大型媒体对象...",
  1272. "NO_CHECK_RESOURCE": "您需要勾選资源才能下載。",
  1273. "NO_VID_URL": "找不到视频网址",
  1274. "SETTING": "设置",
  1275. "AUTO_RENAME": "自动重命名文件",
  1276. "RENAME_SHORTCODE": "重命名文件并包含物件短码",
  1277. "DISABLE_VIDEO_LOOPING": "禁用视频自动循环",
  1278. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "右键单击用户故事区域头像时重定向",
  1279. "FORCE_FETCH_ALL_RESOURCES": "强制抓取帖子中所有资源",
  1280. "DIRECT_DOWNLOAD_WHEN_SINGLE": "直接下载帖子中的单个资源",
  1281. "DIRECT_DOWNLOAD_ALL": "直接下载帖子中的所有资源",
  1282. "MODIFY_VIDEO_VOLUME": "修改视频音量(右击设置)",
  1283. "SCROLL_BUTTON": "为 Reels 页面启用卷动按钮",
  1284. "AUTO_RENAME_INTRO": "将文件自动重新命名为以下格式类型:\n用户名-类型-时间戳.文件类型\n例如:instagram-photo-1670350000.jpg\n\n若设为false,则文件名将保持原样。 \n例如:instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1285. "RENAME_SHORTCODE_INTRO": "自动重命名文件为以下格式类型:\n用户名-类型-短码-时间戳.文件类型\n示例:instagram-photo-CwkxyiVynpW-1670350000.jpg\n\n它仅在[自动重命名文件]设置为 TRUE 时有效。",
  1286. "DISABLE_VIDEO_LOOPING_INTRO": "禁用 Reels 和帖子中的视频自动播放。",
  1287. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "右键单击主页故事区域中的用户头像,重定向到用户的个人资料页面。",
  1288. "FORCE_FETCH_ALL_RESOURCES_INTRO": "通过 Instagram API 强制获取帖子中的所有资源(照片和视频),以取消每个帖子单次抓取三个资源的限制。",
  1289. "DIRECT_DOWNLOAD_WHEN_SINGLE_INTRO": "当帖子只有单一资源时直接下载。",
  1290. "DIRECT_DOWNLOAD_ALL_INTRO": "当您点击下载按钮时,帖子中的所有资源将被直接强制抓取并下载。",
  1291. "MODIFY_VIDEO_VOLUME_INTRO": "修改 Reels 和帖子中的视频播放音量(右击可开启音量设置滑条)。",
  1292. "SCROLL_BUTTON_INTRO": "为 Reels 页面的右下角启用上下卷动按钮。"
  1293. },
  1294. "en-US": {
  1295. "SHOW_DOM_TREE": "Show DOM Tree",
  1296. "SELECT_AND_COPY": "Select All and Copy of the Input Box",
  1297. "DOWNLOAD_DOM_TREE": "Download DOM Tree as Text File",
  1298. "REPORT_GITHUB": "Report Issue On GitHub",
  1299. "REPORT_DISCORD": "Report Issue On Discord Support Server",
  1300. "DEBUG": "Debug Window",
  1301. "CLOSE": "Close",
  1302. "ALL_CHECK": "Select All",
  1303. "BATCH_DOWNLOAD_SELECTED": "Download Selected Resources",
  1304. "BATCH_DOWNLOAD_DIRECT": "Download All Resources",
  1305. "IMG": "Image",
  1306. "VID": "Video",
  1307. "DDL": "Quick Download",
  1308. "DDL_INTRO": "Checking it will direct download current photo/media in the posts.",
  1309. "DW": "Download",
  1310. "THUMBNAIL_INTRO": "Download video thumbnail.",
  1311. "LOAD_BLOB_ONE": "Loading Blob Media...",
  1312. "LOAD_BLOB_MULTIPLE": "Loading Blob Media and others...",
  1313. "LOAD_BLOB_RELOAD": "Detect Blob Media, now reloading...",
  1314. "NO_CHECK_RESOURCE": "You need to check resource to download.",
  1315. "NO_VID_URL": "Can not find video url.",
  1316. "SETTING": "Settings",
  1317. "AUTO_RENAME": "Automatically Rename Files",
  1318. "RENAME_SHORTCODE": "Rename The File and Include Shortcode",
  1319. "DISABLE_VIDEO_LOOPING": "Disable Video Auto-looping",
  1320. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "Redirect When Right-Clicking User Story Picture",
  1321. "FORCE_FETCH_ALL_RESOURCES": "Forcing Fetch All Resources In the Post",
  1322. "DIRECT_DOWNLOAD_WHEN_SINGLE": "Directly Download Single Resource In the Post",
  1323. "DIRECT_DOWNLOAD_ALL": "Directly Download All Resources In the Post",
  1324. "MODIFY_VIDEO_VOLUME": "Modify Video Volume (Right-Click To Set)",
  1325. "SCROLL_BUTTON": "Enable Scroll Buttons For Reels Page",
  1326. "AUTO_RENAME_INTRO": "Auto rename file to format type following:\nUSERNAME-TYPE-TIMESTAMP.FILETYPE\nExample: instagram-photo-1670350000.jpg\n\nIf set to false, the file name will remain as it is.\nExample: instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1327. "RENAME_SHORTCODE_INTRO": "Auto rename file to format type following:\nUSERNAME-TYPE-SHORTCODE-TIMESTAMP.FILETYPE\nExample: instagram-photo-CwkxyiVynpW-1670350000.jpg\n\nIt will ONLY work in [Automatically Rename Files] setting to TRUE.",
  1328. "DISABLE_VIDEO_LOOPING_INTRO": "Disable video auto-looping in reels and posts.",
  1329. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "Redirect to a user's profile page when right-clicking on their user avatar in the story area on the homepage.",
  1330. "FORCE_FETCH_ALL_RESOURCES_INTRO": "Force fetching of all resources (photos and videos) in a post via the Instagram API to remove the limit of three resources per post.",
  1331. "DIRECT_DOWNLOAD_WHEN_SINGLE_INTRO": "Download directly when the post only has a single resource.",
  1332. "DIRECT_DOWNLOAD_ALL_INTRO": "When you click the download button, all resources in the post will be directly forced to be fetched and downloaded.",
  1333. "MODIFY_VIDEO_VOLUME_INTRO": "Modify the video playback volume in Reels and Posts (right-click to open the volume setting slider).",
  1334. "SCROLL_BUTTON_INTRO": "Enable scroll buttons for the lower right corner of Reels page."
  1335. }
  1336. };
  1337. }
  1338.  
  1339. /**
  1340. * _i18n
  1341. * Perform i18n translation
  1342. *
  1343. * @param {String} text
  1344. * @return {void}
  1345. */
  1346. function _i18n(text){
  1347. let userLang = (lang)?lang:"en-US";
  1348. let translate = {
  1349. "zh-TW": function(){
  1350. return translateText()["zh-TW"];
  1351. },
  1352. "zh-HK": function(){
  1353. return translateText()["zh-TW"];
  1354. },
  1355. "zh-MO": function(){
  1356. return translateText()["zh-TW"];
  1357. },
  1358. "zh-CN": function(){
  1359. return translateText()["zh-CN"];
  1360. },
  1361. "en-US": function(){
  1362. return translateText()["en-US"];
  1363. }
  1364. }
  1365.  
  1366. try{
  1367. return translate[lang]()[text];
  1368. }
  1369. catch{
  1370. return translate["en-US"]()[text];
  1371. }
  1372. }
  1373.  
  1374. /**
  1375. * showSetting
  1376. * Show script settings window
  1377. *
  1378. * @return {void}
  1379. */
  1380. function showSetting(){
  1381. $('.IG_SN_DIG').remove();
  1382. IG_createDM();
  1383. $('.IG_SN_DIG #post_info').text('IG Helper Settings');
  1384.  
  1385.  
  1386. for(let name in USER_SETTING){
  1387. $('.IG_SN_DIG .IG_SN_DIG_BODY').append(`<label class="globalSettings" title="${_i18n(name+'_INTRO')}"><span>${_i18n(name)}</span> <input id="${name}" value="box" type="checkbox" ${(USER_SETTING[name])?'checked':''}><div class="chbtn"><div class="rounds"></div></div></label>`);
  1388.  
  1389. if(name === 'MODIFY_VIDEO_VOLUME'){
  1390. $('.IG_SN_DIG .IG_SN_DIG_BODY input[id="'+name+'"]').parent('label').on('contextmenu', function(e){
  1391. e.preventDefault();
  1392. if($(this).find('#tempWrapper').length === 0){
  1393. $(this).append('<div id="tempWrapper"></div>');
  1394. $(this).children('#tempWrapper').append('<input value="' + VIDEO_VOLUME + '" type="range" min="0" max="1" step="0.05" />');
  1395. $(this).children('#tempWrapper').append('<input value="' + VIDEO_VOLUME + '" step="0.05" type="number" />');
  1396. $(this).children('#tempWrapper').append('<svg width="26" height="26" class="IG_SN_DIG_BTN" style="cursor:pointer;position:absolute;right:5px;top:0px;fill: rgb(var(--ig-primary-text));" 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>');
  1397. }
  1398. });
  1399. }
  1400. }
  1401. }
  1402.  
  1403. /**
  1404. * showDebugDOM
  1405. * Show full DOM tree
  1406. *
  1407. * @return {void}
  1408. */
  1409. function showDebugDOM(){
  1410. $('.IG_SN_DIG').remove();
  1411. IG_createDM();
  1412. $('.IG_SN_DIG #post_info').text('IG Debug DOM Tree');
  1413.  
  1414. $('.IG_SN_DIG .IG_SN_DIG_BODY').append(`<textarea style="width:100%;box-sizing: border-box;height:300px;" readonly></textarea>`);
  1415. $('.IG_SN_DIG .IG_SN_DIG_BODY').append(`<span style="display:block;text-align:center;">`);
  1416. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_DISPLAY_DOM_TREE"><a>${_i18n('SHOW_DOM_TREE')}</a></button>`);
  1417. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_SELECT_DOM_TREE"><a>${_i18n('SELECT_AND_COPY')}</a></button>`);
  1418. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_DOWNLOAD_DOM_TREE"><a>${_i18n('DOWNLOAD_DOM_TREE')}</a></button><br/>`);
  1419. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_REPORT_GITHUB"><a href="https://github.com/SN-Koarashi/ig-helper/issues" target="_blank">${_i18n('REPORT_GITHUB')}</a></button>`);
  1420. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_REPORT_DISCORD"><a href="https://discord.gg/Sh8HJ4d" target="_blank">${_i18n('REPORT_DISCORD')}</a></button>`);
  1421. }
  1422.  
  1423. // Running if document is ready
  1424. $(function(){
  1425. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_DISPLAY_DOM_TREE',function(){
  1426. let text = $('div[id^="mount"]')[0];
  1427. $('.IG_SN_DIG .IG_SN_DIG_BODY textarea').text("Location: " + location.pathname + "\nDOM Tree:\n" + text.innerHTML);
  1428. });
  1429.  
  1430. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_SELECT_DOM_TREE',function(){
  1431. $('.IG_SN_DIG .IG_SN_DIG_BODY textarea').select();
  1432. document.execCommand('copy');
  1433. });
  1434.  
  1435. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_DOWNLOAD_DOM_TREE',function(){
  1436. var text = ($('.IG_SN_DIG .IG_SN_DIG_BODY textarea').text().length > 0)?$('.IG_SN_DIG .IG_SN_DIG_BODY textarea').text():"Location: " + location.pathname + "\nDOM Tree:\n" +$('div[id^="mount"]')[0].innerHTML;
  1437. var a = document.createElement("a");
  1438. var file = new Blob([text], {type: "text/plain"});
  1439. a.href = URL.createObjectURL(file);
  1440. a.download = "DOMTree.txt";
  1441.  
  1442. document.body.appendChild(a);
  1443. a.click();
  1444. a.remove();
  1445. });
  1446.  
  1447. // Close the download dialog if user click the close icon
  1448. $('body').on('click','.IG_SN_DIG_BTN,.IG_SN_DIG_BG',function(){
  1449. if($(this).parent('#tempWrapper').length > 0){
  1450. $(this).parent('#tempWrapper').fadeOut(250, function(){
  1451. $(this).remove();
  1452. });
  1453. }
  1454. else{
  1455. $('.IG_SN_DIG').remove();
  1456. }
  1457. });
  1458.  
  1459. $(window).keydown(function(e){
  1460. // Hot key [Alt+Q] to close the download dialog
  1461. if (e.keyCode == '81' && e.altKey){
  1462. $('.IG_SN_DIG').remove();
  1463. e.preventDefault();
  1464. }
  1465. // Hot key [Alt+W] to open the settings dialog
  1466. if (e.keyCode == '87' && e.altKey){
  1467. showSetting();
  1468. e.preventDefault();
  1469. }
  1470.  
  1471. // Hot key [Alt+Z] to open the settings dialog
  1472. if (e.keyCode == '90' && e.altKey){
  1473. showDebugDOM();
  1474. e.preventDefault();
  1475. }
  1476. });
  1477.  
  1478. $('body').on('change', '.IG_SN_DIG input',function(e){
  1479. var name = $(this).attr('id');
  1480.  
  1481. if(name && USER_SETTING[name] !== undefined){
  1482. let isChecked = $(this).prop('checked');
  1483. GM_setValue(name, isChecked);
  1484. USER_SETTING[name] = isChecked;
  1485.  
  1486. console.log('user settings', name, isChecked);
  1487. }
  1488. });
  1489.  
  1490. $('body').on('click', '.IG_SN_DIG .globalSettings',function(e){
  1491. if($(this).find('#tempWrapper').length > 0){
  1492. e.preventDefault();
  1493. }
  1494. });
  1495.  
  1496. $('body').on('change', '.IG_SN_DIG #tempWrapper input',function(){
  1497. let value = $(this).val();
  1498.  
  1499. if($(this).attr('type') == 'range'){
  1500. $(this).next().val(value);
  1501. }
  1502. else{
  1503. $(this).prev().val(value);
  1504. }
  1505.  
  1506. if(value >= 0 && value <= 1){
  1507. VIDEO_VOLUME = value;
  1508. GM_setValue('G_VIDEO_VOLUME', value);
  1509. }
  1510. });
  1511.  
  1512. $('body').on('input', '.IG_SN_DIG #tempWrapper input',function(e){
  1513. if($(this).attr('type') == 'range'){
  1514. let value = $(this).val();
  1515. $(this).next().val(value);
  1516. }
  1517. else{
  1518. let value = $(this).val();
  1519. if(value >= 0 && value <= 1){
  1520. $(this).prev().val(value);
  1521. }
  1522. else{
  1523. if(value < 0){
  1524. $(this).val(0);
  1525. }
  1526. else{
  1527. $(this).val(1);
  1528. }
  1529. }
  1530. }
  1531. });
  1532.  
  1533.  
  1534. $('body').on('click','a[data-needed="direct"]',async function(){
  1535. triggerLinkElement(this);
  1536. });
  1537.  
  1538. // Running if user left-click download icon in stories
  1539. $('body').on('click','.IG_DWSTORY',function(){
  1540. onStory(true);
  1541. });
  1542.  
  1543. // Running if user left-click download icon in stories
  1544. $('body').on('click','.IG_DWSTORY_THUMBNAIL',function(){
  1545. onStoryThumbnail(true);
  1546. });
  1547.  
  1548. // Running if user left-click download icon in profile
  1549. $('body').on('click','.IG_DWPROFILE',function(e){
  1550. e.stopPropagation();
  1551. onProfileAvatar(true);
  1552. });
  1553.  
  1554. // Running if user left-click download icon in highlight stories
  1555. $('body').on('click','.IG_DWHISTORY',function(){
  1556. onHighlightsStory(true);
  1557. });
  1558.  
  1559. // Running if user left-click download icon in highlight stories
  1560. $('body').on('click','.IG_DWHISTORY_THUMBNAIL',function(){
  1561. onHighlightsStoryThumbnail(true);
  1562. });
  1563.  
  1564. // Running if user left-click download icon in reels
  1565. $('body').on('click','.IG_REELS',function(){
  1566. onReels(true,true);
  1567. });
  1568.  
  1569. // Running if user left-click download icon in reels
  1570. $('body').on('click','.IG_REELS_THUMBNAIL',function(){
  1571. onReels(true,false);
  1572. });
  1573.  
  1574. // Running if user right-click profile picture in stories area
  1575. $('body').on('contextmenu','button[role="menuitem"]',function(){
  1576. if(location.href === 'https://www.instagram.com/' && USER_SETTING.REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE){
  1577. if($(this).find('canvas._aarh').length > 0){
  1578. location.href = 'https://www.instagram.com/'+$(this).children('div').last().text();
  1579. }
  1580. }
  1581. });
  1582.  
  1583. $('body').on('change', '.IG_SN_DIG_TITLE .checkbox', function(){
  1584. var isChecked = $(this).find('input').prop('checked');
  1585. $('.IG_SN_DIG_BODY .inner_box').each(function(){
  1586. $(this).prop('checked', isChecked);
  1587. });
  1588. });
  1589.  
  1590. $('body').on('change', '.IG_SN_DIG_BODY .inner_box', function(){
  1591. var checked = $('.IG_SN_DIG_BODY .inner_box:checked').length;
  1592. var total = $('.IG_SN_DIG_BODY .inner_box').length;
  1593.  
  1594.  
  1595. $('.IG_SN_DIG_TITLE .checkbox').find('input').prop('checked', checked == total);
  1596. });
  1597.  
  1598. $('body').on('click', '.IG_SN_DIG_TITLE #batch_download_selected', function(){
  1599. let index = 0;
  1600. $('.IG_SN_DIG_BODY a[data-needed="direct"]').each(function(){
  1601. if($(this).prev().children('input').prop('checked')){
  1602. $(this).click();
  1603. index++;
  1604. }
  1605. });
  1606.  
  1607. if(index == 0){
  1608. alert(_i18n('NO_CHECK_RESOURCE'));
  1609. }
  1610. });
  1611.  
  1612. $('body').on('click', '.IG_SN_DIG_TITLE #batch_download_direct', function(){
  1613. $('.IG_SN_DIG_BODY a[data-needed="direct"]').each(function(){
  1614. $(this).click();
  1615. });
  1616. });
  1617. });
  1618. })(jQuery);