IG小助手

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

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

  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.18.6
  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. // @description:ro Descărcarea este posibilă atât pentru fotografiile și videoclipurile din postări, cât și pentru storyuri, reels sau poze de profil.
  15. // @author SN-Koarashi (5026)
  16. // @match https://*.instagram.com/*
  17. // @grant GM_addStyle
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @grant GM_xmlhttpRequest
  21. // @grant GM_registerMenuCommand
  22. // @grant GM_getResourceText
  23. // @connect i.instagram.com
  24. // @require https://code.jquery.com/jquery-3.6.3.min.js#sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=
  25. // @resource INTERNAL_CSS https://raw.githubusercontent.com/SN-Koarashi/ig-helper/master/style.css
  26. // @supportURL https://github.com/SN-Koarashi/ig-helper/
  27. // @contributionURL https://ko-fi.com/snkoarashi
  28. // @icon https://www.google.com/s2/favicons?domain=www.instagram.com
  29. // @compatible firefox >= 87
  30. // @compatible chrome >= 90
  31. // @compatible edge >= 90
  32. // @license GPL-3.0-only
  33. // ==/UserScript==
  34.  
  35. (function($) {
  36. 'use strict';
  37. // Icon download by https://www.flaticon.com/authors/pixel-perfect
  38.  
  39. /******** USER SETTINGS ********/
  40. // !!! DO NOT CHANGE THIS AREA !!!
  41. // PLEASE CHANGE SETTING WITH MENU
  42. const USER_SETTING = {
  43. 'AUTO_RENAME': (GM_getValue('AUTO_RENAME') != null && typeof GM_getValue('AUTO_RENAME') === 'boolean')?GM_getValue('AUTO_RENAME'):true,
  44. 'RENAME_SHORTCODE': (GM_getValue('RENAME_SHORTCODE') != null && typeof GM_getValue('RENAME_SHORTCODE') === 'boolean')?GM_getValue('RENAME_SHORTCODE'):true,
  45. 'DISABLE_VIDEO_LOOPING': (GM_getValue('DISABLE_VIDEO_LOOPING') != null && typeof GM_getValue('DISABLE_VIDEO_LOOPING') === 'boolean')?GM_getValue('DISABLE_VIDEO_LOOPING'):false,
  46. 'REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE': (GM_getValue('REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE') != null && typeof GM_getValue('REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE') === 'boolean')?GM_getValue('REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE'):false,
  47. 'FORCE_FETCH_ALL_RESOURCES': (GM_getValue('FORCE_FETCH_ALL_RESOURCES') != null && typeof GM_getValue('FORCE_FETCH_ALL_RESOURCES') === 'boolean')?GM_getValue('FORCE_FETCH_ALL_RESOURCES'):false,
  48. 'DIRECT_DOWNLOAD_VISABLE_RESOURCE': (GM_getValue('DIRECT_DOWNLOAD_VISABLE_RESOURCE') != null && typeof GM_getValue('DIRECT_DOWNLOAD_VISABLE_RESOURCE') === 'boolean')?GM_getValue('DIRECT_DOWNLOAD_VISABLE_RESOURCE'):false,
  49. 'DIRECT_DOWNLOAD_ALL': (GM_getValue('DIRECT_DOWNLOAD_ALL') != null && typeof GM_getValue('DIRECT_DOWNLOAD_ALL') === 'boolean')?GM_getValue('DIRECT_DOWNLOAD_ALL'):false,
  50. 'MODIFY_VIDEO_VOLUME': (GM_getValue('MODIFY_VIDEO_VOLUME') != null && typeof GM_getValue('MODIFY_VIDEO_VOLUME') === 'boolean')?GM_getValue('MODIFY_VIDEO_VOLUME'):false,
  51. 'SCROLL_BUTTON': (GM_getValue('SCROLL_BUTTON') != null && typeof GM_getValue('SCROLL_BUTTON') === 'boolean')?GM_getValue('SCROLL_BUTTON'):true,
  52. 'FORCE_RESOURCE_VIA_MEDIA': (GM_getValue('FORCE_RESOURCE_VIA_MEDIA') != null && typeof GM_getValue('FORCE_RESOURCE_VIA_MEDIA') === 'boolean')?GM_getValue('FORCE_RESOURCE_VIA_MEDIA'):false
  53. };
  54. var VIDEO_VOLUME = (GM_getValue('G_VIDEO_VOLUME'))?GM_getValue('G_VIDEO_VOLUME'):1;
  55. /*******************************/
  56.  
  57. const checkInterval = 250;
  58. const lang = navigator.language || navigator.userLanguage;
  59. const style = GM_getResourceText("INTERNAL_CSS");
  60.  
  61. GM_addStyle(style);
  62. GM_registerMenuCommand(_i18n('SETTING'), () => {
  63. showSetting();
  64. });
  65. GM_registerMenuCommand(_i18n('DEBUG'), () => {
  66. showDebugDOM();
  67. });
  68.  
  69. var currentURL = location.href;
  70. var currentHeight = $(document).height();
  71. var firstStarted = false;
  72. var pageLoaded = false;
  73.  
  74. var GL_postPath;
  75. var GL_username;
  76. var GL_repeat;
  77. var GL_dataCache = {
  78. stories: {},
  79. highlights: {}
  80. };
  81. var GL_observer = new MutationObserver(function (mutation, owner) {
  82. onReadyMyDW();
  83. });
  84.  
  85. // Main Timer
  86. var timer = setInterval(function(){
  87. currentHeight = $(document).height();
  88. // Call Instagram dialog function if url changed.
  89. if(currentURL != location.href || !firstStarted || !pageLoaded){
  90. clearInterval(GL_repeat);
  91. pageLoaded = false;
  92. firstStarted = true;
  93. currentURL = location.href;
  94.  
  95. if(location.href.startsWith("https://www.instagram.com/p/") || location.href.startsWith("https://www.instagram.com/reel/")){
  96. GL_dataCache.stories = {};
  97.  
  98. console.log('isDialog');
  99.  
  100. // This is to prevent the detection of the "Modify Video Volume" setting from being too slow.
  101. setTimeout(()=>{
  102. onReadyMyDW(false);
  103. }, 5);
  104.  
  105. // This is a delayed function call that prevents the dialog element from appearing before the function is called.
  106. setTimeout(()=>{
  107. onReadyMyDW(false);
  108. }, 250);
  109. setTimeout(()=>{
  110. onReadyMyDW(false);
  111. }, 450);
  112.  
  113. pageLoaded = true;
  114. }
  115.  
  116. if(location.href.startsWith("https://www.instagram.com/reels/")){
  117. console.log('isReels');
  118. setTimeout(()=>{
  119. onReels(false);
  120. },150);
  121. pageLoaded = true;
  122. }
  123.  
  124. if(location.href.split("?")[0] == "https://www.instagram.com/"){
  125. GL_dataCache.stories = {};
  126.  
  127. console.log('isHomepage');
  128. setTimeout(()=>{
  129. onReadyMyDW(false);
  130. },150);
  131.  
  132. const element = $('div[id^="mount"] > div > div div > section > main div:not([class]):not([style]) > div > article').parent()[0];
  133. GL_observer.observe(element, {
  134. childList: true
  135. });
  136.  
  137. pageLoaded = true;
  138. }
  139. 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\.\-_]+)\/?(tagged|reels)?\/?$/ig) && !location.href.match(/^(https:\/\/www\.instagram\.com\/(stories|explore)\/?)/ig)){
  140. console.log('isProfile');
  141. setTimeout(()=>{
  142. onProfileAvatar(false);
  143. },150);
  144. pageLoaded = true;
  145. }
  146.  
  147. if(!pageLoaded){
  148. // Call Instagram stories function
  149. if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/highlights\/)/ig)){
  150. GL_dataCache.highlights = {};
  151.  
  152. console.log('isHighlightsStory');
  153. onHighlightsStory(false);
  154. GL_repeat = setInterval(()=>{
  155. onHighlightsStoryThumbnail(false);
  156. },checkInterval);
  157.  
  158. if($(".IG_DWHISTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  159. }
  160. else if(location.href.match(/^(https:\/\/www\.instagram\.com\/stories\/)/ig)){
  161. console.log('isStory');
  162. onStory(false);
  163. onStoryThumbnail(false);
  164.  
  165. if($(".IG_DWSTORY").length) setTimeout(()=>{pageLoaded = true;},150);
  166. }
  167. else{
  168. pageLoaded = false;
  169. // Remove the download icon
  170. $('.IG_DWSTORY').remove();
  171. $('.IG_DWSTORY_THUMBNAIL').remove();
  172. }
  173. }
  174.  
  175. }
  176. },checkInterval);
  177.  
  178. // Call general function when user scroll the page
  179. /*
  180. $(document).scroll(function(){
  181. if(currentHeight != $(this).height() && location.href.split("?")[0] == "https://www.instagram.com/"){
  182. onReadyMyDW();
  183. }
  184. });
  185. */
  186.  
  187. /**
  188. * onProfileAvatar
  189. * Trigger user avatar download event or button display event.
  190. *
  191. * @param {Boolean} isDownload - Check if it is a download operation
  192. * @return {void}
  193. */
  194. async function onProfileAvatar(isDownload){
  195. if(isDownload){
  196. let date = new Date().getTime();
  197. let timestamp = Math.floor(date / 1000);
  198. let username = location.pathname.replaceAll(/(reels|tagged)\/$/ig,'').split('/').filter(s => s.length > 0).at(-1);
  199. let userInfo = await getUserId(username);
  200. try{
  201. let dataURL = await getUserHighSizeProfile(userInfo.user.pk);
  202. saveFiles(dataURL,username,"avatar",timestamp,'jpg');
  203. }
  204. catch(err){
  205. saveFiles(userInfo.user.profile_pic_url,username,"avatar",timestamp,'jpg');
  206. }
  207. }
  208. else{
  209. // Add the profile download button
  210. if(!$('.IG_DWPROFILE').length){
  211. let profileTimer = setInterval(()=>{
  212. if($('.IG_DWPROFILE').length){
  213. clearInterval(profileTimer);
  214. return;
  215. }
  216.  
  217. $('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>`);
  218. $('body > div main canvas._aarh, body > div main div._aadm').parent().css('position','relative');
  219. },150);
  220. }
  221. }
  222. }
  223.  
  224. /**
  225. * onHighlightsStory
  226. * Trigger user's highlight download event or button display event.
  227. *
  228. * @param {Boolean} isDownload - Check if it is a download operation
  229. * @return {void}
  230. */
  231. async function onHighlightsStory(isDownload, isPreview){
  232. if(isDownload){
  233. let date = new Date().getTime();
  234. let timestamp = Math.floor(date / 1000);
  235. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  236. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length ||
  237. $('body > div section:visible > div > div:not([class]) > div > div div.x1ned7t2.x78zum5 div.x1caxmr6').length ||
  238. $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div').find('div div.x1ned7t2.x78zum5 div.x1caxmr6').length;
  239. let username = "";
  240. let target = 0;
  241.  
  242. if(GL_dataCache.highlights[highlightId]){
  243. console.log('Fetch from memory cache:', highlightId);
  244.  
  245. let totIndex = GL_dataCache.highlights[highlightId].data.reels_media[0].items.length;
  246. username = GL_dataCache.highlights[highlightId].data.reels_media[0].owner.username;
  247. target = GL_dataCache.highlights[highlightId].data.reels_media[0].items[totIndex-nowIndex];
  248. }
  249. else{
  250. let highStories = await getHighlightStories(highlightId);
  251. let totIndex = highStories.data.reels_media[0].items.length;
  252. username = highStories.data.reels_media[0].owner.username;
  253. target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  254.  
  255. GL_dataCache.highlights[highlightId] = highStories;
  256. }
  257.  
  258. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  259. let result = await getMediaInfo(target.id);
  260.  
  261. if(result.status === 'ok'){
  262. if(result.items[0].video_versions){
  263. if(isPreview){
  264. openNewTab(result.items[0].video_versions[0].url);
  265. }
  266. else{
  267. saveFiles(result.items[0].video_versions[0].url, username,"highlights",timestamp,'mp4');
  268. }
  269. }
  270. else{
  271. if(isPreview){
  272. openNewTab(result.items[0].image_versions2.candidates[0].url);
  273. }
  274. else{
  275. saveFiles(result.items[0].image_versions2.candidates[0].url, username,"highlights",timestamp,'jpg');
  276. }
  277. }
  278. }
  279. else{
  280. alert('fetch failed from Media API, ' + result.message);
  281. console.log(result);
  282. }
  283. }
  284. else{
  285. if(target.is_video){
  286. if(isPreview){
  287. openNewTab(target.video_resources.at(-1).src,username);
  288. }
  289. else{
  290. saveFiles(target.video_resources.at(-1).src,username,"highlights",timestamp,'mp4', highlightId);
  291. }
  292. }
  293. else{
  294. if(isPreview){
  295. openNewTab(target.display_resources.at(-1).src,username);
  296. }
  297. else{
  298. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg', highlightId);
  299. }
  300. }
  301. }
  302. }
  303. else{
  304. // Add the stories download button
  305. if(!$('.IG_DWHISTORY').length){
  306. let $element = null;
  307.  
  308. // Default detecter (section layout mode)
  309. if($('body > div section._ac0a').length > 0){
  310. $element = $('body > div section:visible._ac0a');
  311. }
  312. else{
  313. $element = $('body > div section:visible > div > div[style]:not([class])');
  314. $element.css('position','relative');
  315. }
  316.  
  317. // Detecter for div layout mode
  318. // GitHub issue #3: DiceMast3r
  319. if($element.length === 0){
  320. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  321. let nowSize = 0;
  322.  
  323. $$element.each(function(){
  324. if($(this).width() > nowSize){
  325. nowSize = $(this).width();
  326. $element = $(this);
  327. }
  328. });
  329. }
  330.  
  331.  
  332. if($element != null){
  333. //$element.css('position','relative');
  334. $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>`);
  335. $element.append(`<div title="${_i18n("NEW_TAB")}" class="IG_DWHINEWTAB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg></div>`);
  336. }
  337. }
  338. }
  339. }
  340.  
  341. /**
  342. * onHighlightsStoryThumbnail
  343. * Trigger user's highlight video thumbnail download event or button display event.
  344. *
  345. * @param {Boolean} isDownload - Check if it is a download operation
  346. * @return {void}
  347. */
  348. async function onHighlightsStoryThumbnail(isDownload){
  349. if(isDownload){
  350. let date = new Date().getTime();
  351. let timestamp = Math.floor(date / 1000);
  352. let highlightId = location.href.replace(/\/$/ig,'').split('/').at(-1);
  353. let username = "";
  354. let nowIndex = $("body > div section._ac0a header._ac0k > ._ac3r ._ac3n ._ac3p[style]").length ||
  355. $('body > div section:visible > div > div:not([class]) > div > div div.x1ned7t2.x78zum5 div.x1caxmr6').length ||
  356. $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div').find('div div.x1ned7t2.x78zum5 div.x1caxmr6').length;
  357. let target = "";
  358.  
  359. if(GL_dataCache.highlights[highlightId]){
  360. console.log('Fetch from memory cache:', highlightId);
  361.  
  362. let totIndex = GL_dataCache.highlights[highlightId].data.reels_media[0].items.length;
  363. username = GL_dataCache.highlights[highlightId].data.reels_media[0].owner.username;
  364. target = GL_dataCache.highlights[highlightId].data.reels_media[0].items[totIndex-nowIndex];
  365. }
  366. else{
  367. let highStories = await getHighlightStories(highlightId);
  368. let totIndex = highStories.data.reels_media[0].items.length;
  369. username = highStories.data.reels_media[0].owner.username;
  370. target = highStories.data.reels_media[0].items[totIndex-nowIndex];
  371.  
  372. GL_dataCache.highlights[highlightId] = highStories;
  373. }
  374.  
  375. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  376. let result = await getMediaInfo(target.id);
  377.  
  378. if(result.status === 'ok'){
  379. saveFiles(result.items[0].image_versions2.candidates[0].url, username,"highlights",timestamp,'jpg');
  380. }
  381. else{
  382. alert('fetch failed from Media API, ' + result.message);
  383. console.log(result);
  384. }
  385. }
  386. else{
  387. saveFiles(target.display_resources.at(-1).src,username,"highlights",timestamp,'jpg', highlightId);
  388. }
  389. }
  390. else{
  391. if($('body > div section video.xh8yej3').length){
  392. // Add the stories download button
  393. if(!$('.IG_DWHISTORY_THUMBNAIL').length){
  394. let $element = null;
  395.  
  396. // Default detecter (section layout mode)
  397. if($('body > div section._ac0a').length > 0){
  398. $element = $('body > div section:visible._ac0a');
  399. }
  400. else{
  401. $element = $('body > div section:visible > div > div[style]:not([class])');
  402. $element.css('position','relative');
  403. }
  404.  
  405. // Detecter for div layout mode
  406. // GitHub issue #3: DiceMast3r
  407. if($element.length === 0){
  408. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  409. let nowSize = 0;
  410.  
  411. $$element.each(function(){
  412. if($(this).width() > nowSize){
  413. nowSize = $(this).width();
  414. $element = $(this);
  415. }
  416. });
  417. }
  418.  
  419. if($element != null){
  420. $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>`);
  421. }
  422. }
  423. }
  424. else{
  425. $('.IG_DWHISTORY_THUMBNAIL').remove();
  426. }
  427. }
  428. }
  429.  
  430. /**
  431. * onStory
  432. * Trigger user's story download event or button display event.
  433. *
  434. * @param {Boolean} isDownload - Check if it is a download operation
  435. * @return {void}
  436. */
  437. async function onStory(isDownload,isForce,isPreview){
  438. if(isDownload){
  439. let date = new Date().getTime();
  440. let timestamp = Math.floor(date / 1000);
  441. let username = $("body > div section._ac0a header._ac0k ._ac0l a + div a").first().text() || location.pathname.split('/').at(2);
  442.  
  443. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  444. let mediaId = null;
  445.  
  446. let userInfo = await getUserId(username);
  447. let userId = userInfo.user.pk;
  448. let stories = await getStories(userId);
  449.  
  450. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  451. if($(this).hasClass('x1lix1fw')){
  452. if($(this).children().length > 0){
  453. mediaId = stories.data.reels_media[0].items[index].id;
  454. }
  455. }
  456. });
  457.  
  458. if(mediaId == null){
  459. mediaId = location.pathname.split('/').filter(s => s.length > 0).at(-1);
  460. }
  461.  
  462. let result = await getMediaInfo(mediaId);
  463.  
  464. if(result.status === 'ok'){
  465. if(result.items[0].video_versions){
  466. if(isPreview){
  467. openNewTab(result.items[0].video_versions[0].url);
  468. }
  469. else{
  470. saveFiles(result.items[0].video_versions[0].url, username,"stories",timestamp,'mp4');
  471. }
  472. }
  473. else{
  474. if(isPreview){
  475. openNewTab(result.items[0].image_versions2.candidates[0].url);
  476. }
  477. else{
  478. saveFiles(result.items[0].image_versions2.candidates[0].url, username,"stories",timestamp,'jpg');
  479. }
  480. }
  481. }
  482. else{
  483. alert('fetch failed from Media API, ' + result.message);
  484. console.log(result);
  485. }
  486.  
  487. return;
  488. }
  489.  
  490. if($('body > div section:visible video[playsinline]').length > 0){
  491. // Download stories if it is video
  492. let type = "mp4";
  493. let videoURL = "";
  494. let targetURL = location.pathname.replace(/\/$/ig,'').split("/").at(-1);
  495.  
  496. if(GL_dataCache.stories[username] && !isForce){
  497. console.log('Fetch from memory cache:', username);
  498. GL_dataCache.stories[username].data.reels_media[0].items.forEach(item => {
  499. if(item.id == targetURL){
  500. videoURL = item.video_resources[0].src;
  501. }
  502. });
  503.  
  504. if(videoURL.length == 0){
  505. console.log('Memory cache not found, try fetch from API:', username);
  506. onStory(true,true);
  507. return;
  508. }
  509. }
  510. else{
  511. let userInfo = await getUserId(username);
  512. let userId = userInfo.user.pk;
  513. let stories = await getStories(userId);
  514.  
  515. stories.data.reels_media[0].items.forEach(item => {
  516. if(item.id == targetURL){
  517. videoURL = item.video_resources[0].src;
  518. }
  519. });
  520.  
  521. // GitHub issue #4: thinkpad4
  522. if(videoURL.length == 0){
  523. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  524. if($(this).hasClass('x1lix1fw')){
  525. if($(this).children().length > 0){
  526. videoURL = stories.data.reels_media[0].items[index].video_resources[0].src;
  527.  
  528. }
  529. }
  530. });
  531. }
  532.  
  533. GL_dataCache.stories[username] = stories;
  534. }
  535.  
  536. if(videoURL.length == 0){
  537. alert(_i18n("NO_VID_URL"));
  538. }
  539. else{
  540. if(isPreview){
  541. openNewTab(videoURL);
  542. }
  543. else{
  544. saveFiles(videoURL,username,"stories",timestamp,type);
  545. }
  546. }
  547. }
  548. else{
  549. // Download stories if it is image
  550. let srcset = $('body > div section:visible img[referrerpolicy][class], body > div section:visible img[crossorigin][class]:not([alt])').attr('srcset')?.split(',')[0]?.split(' ')[0];
  551. let link = (srcset)?srcset:$('body > div section:visible img[referrerpolicy][class], body > div section:visible img[crossorigin][class]:not([alt])').attr('src');
  552.  
  553. if(!link){
  554. // _aa63 mean stories picture in stories page (not avatar)
  555. let $element = $('body > div section:visible img._aa63');
  556. link = ($element.attr('srcset'))?$element.attr('srcset')?.split(',')[0]?.split(' ')[0]:$element.attr('src');
  557. }
  558.  
  559. let downloadLink = link;
  560. let type = 'jpg';
  561.  
  562. if(isPreview){
  563. openNewTab(downloadLink);
  564. }
  565. else{
  566. saveFiles(downloadLink,username,"stories",timestamp,type);
  567. }
  568. }
  569. }
  570. else{
  571. // Add the stories download button
  572. let style = "position: absolute;right:-40px;top:15px;padding:5px;line-height:1;background:#fff;border-radius: 5px;cursor:pointer;";
  573. if(!$('.IG_DWSTORY').length){
  574. GL_dataCache.stories = {};
  575. let $element = null;
  576. // Default detecter (section layout mode)
  577. if($('body > div section._ac0a').length > 0){
  578. $element = $('body > div section:visible._ac0a');
  579. }
  580. // detecter (single story layout mode)
  581. else{
  582. $element = $('body > div section:visible > div > div[style]:not([class])');
  583. $element.css('position','relative');
  584. }
  585.  
  586.  
  587. // Detecter for div layout mode
  588. // GitHub issue #3: DiceMast3r
  589. if($element.length === 0){
  590. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  591. let nowSize = 0;
  592.  
  593. $$element.each(function(){
  594. if($(this).width() > nowSize){
  595. nowSize = $(this).width();
  596. $element = $(this);
  597. }
  598. });
  599. }
  600.  
  601.  
  602. if($element != null){
  603. $element.css('position','relative');
  604. $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>`);
  605. $element.append(`<div title="${_i18n("NEW_TAB")}" class="IG_DWNEWTAB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg></div>`);
  606. }
  607. }
  608. }
  609. }
  610.  
  611. /**
  612. * onStoryThumbnail
  613. * Trigger user's story video thumbnail download event or button display event.
  614. *
  615. * @param {Boolean} isDownload - Check if it is a download operation
  616. * @return {void}
  617. */
  618. async function onStoryThumbnail(isDownload,isForce){
  619. if(isDownload){
  620. // Download stories if it is video
  621. let date = new Date().getTime();
  622. let timestamp = Math.floor(date / 1000);
  623. let type = 'jpg';
  624. let username = $("body > div section._ac0a header._ac0k ._ac0l a + div a").first().text() || location.pathname.split('/').at(2);
  625. 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;';
  626. // Download thumbnail
  627. let targetURL = location.pathname.replace(/\/$/ig,'').split("/").at(-1);
  628. let videoThumbnailURL = "";
  629.  
  630. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  631. let mediaId = null;
  632.  
  633. let userInfo = await getUserId(username);
  634. let userId = userInfo.user.pk;
  635. let stories = await getStories(userId);
  636.  
  637. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  638. if($(this).hasClass('x1lix1fw')){
  639. if($(this).children().length > 0){
  640. mediaId = stories.data.reels_media[0].items[index].id;
  641. }
  642. }
  643. });
  644.  
  645. if(mediaId == null){
  646. mediaId = location.pathname.split('/').filter(s => s.length > 0).at(-1);
  647. }
  648.  
  649. let result = await getMediaInfo(mediaId);
  650.  
  651. if(result.status === 'ok'){
  652. saveFiles(result.items[0].image_versions2.candidates[0].url, username,"stories",timestamp,'jpg');
  653.  
  654. }
  655. else{
  656. alert('fetch failed from Media API, ' + result.message);
  657. console.log(result);
  658. }
  659.  
  660. return;
  661. }
  662.  
  663. if(GL_dataCache.stories[username] && !isForce){
  664. console.log('Fetch from memory cache:', username);
  665. GL_dataCache.stories[username].data.reels_media[0].items.forEach(item => {
  666. if(item.id == targetURL){
  667. videoThumbnailURL = item.display_url;
  668. }
  669. });
  670.  
  671. if(videoThumbnailURL.length == 0){
  672. console.log('Memory cache not found, try fetch from API:', username);
  673. onStoryThumbnail(true,true);
  674. return;
  675. }
  676. }
  677. else{
  678. let userInfo = await getUserId(username);
  679. let userId = userInfo.user.pk;
  680. let stories = await getStories(userId);
  681.  
  682. stories.data.reels_media[0].items.forEach(item => {
  683. if(item.id == targetURL){
  684. videoThumbnailURL = item.display_url;
  685. }
  686. });
  687.  
  688. // GitHub issue #4: thinkpad4
  689. if(videoThumbnailURL.length == 0){
  690. $('body > div section:visible div.x1ned7t2.x78zum5 > div').each(function(index){
  691. if($(this).hasClass('x1lix1fw')){
  692. if($(this).children().length > 0){
  693. videoThumbnailURL = stories.data.reels_media[0].items[index].display_url;
  694. }
  695. }
  696. });
  697. }
  698. }
  699.  
  700. saveFiles(videoThumbnailURL,username,"thumbnail",timestamp,type);
  701. }
  702. else{
  703. if($('body > div section video.xh8yej3').length){
  704. // Add the stories download button
  705. if(!$('.IG_DWSTORY_THUMBNAIL').length){
  706. let $element = null;
  707. // Default detecter (section layout mode)
  708. if($('body > div section._ac0a').length > 0){
  709. $element = $('body > div section:visible._ac0a');
  710. }
  711. // detecter (single story layout mode)
  712. else{
  713. $element = $('body > div section:visible > div > div[style]:not([class])');
  714. $element.css('position','relative');
  715. }
  716.  
  717. // Detecter for div layout mode
  718. // GitHub issue #3: DiceMast3r
  719. if($element.length === 0){
  720. let $$element = $('body > div div:not([hidden]) section:visible > div div[style]:not([class]) > div');
  721. let nowSize = 0;
  722.  
  723. $$element.each(function(){
  724. if($(this).width() > nowSize){
  725. nowSize = $(this).width();
  726. $element = $(this);
  727. }
  728. });
  729. }
  730.  
  731.  
  732. if($element != null){
  733. $element.css('position','relative');
  734. $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>`);
  735. }
  736. }
  737. }
  738. else{
  739. $('.IG_DWSTORY_THUMBNAIL').remove();
  740. }
  741. }
  742. }
  743.  
  744. /**
  745. * onReels
  746. * Trigger user's reels download event or button display event.
  747. *
  748. * @param {Boolean} isDownload - Check if it is a download operation
  749. * @param {Boolean} isVideo - Check if reel is a video element
  750. * @return {void}
  751. */
  752. async function onReels(isDownload, isVideo, isPreview){
  753. if(isDownload){
  754. let reelsPath = location.href.split('?').at(0).split('instagram.com/reels/').at(-1).replaceAll('/','');
  755. let data = await getBlobMedia(reelsPath);
  756. let timestamp = new Date().getTime();
  757.  
  758. if(isVideo && data.shortcode_media.is_video){
  759. if(isPreview){
  760. openNewTab(data.shortcode_media.video_url);
  761. }
  762. else{
  763. let type = 'mp4';
  764. saveFiles(data.shortcode_media.video_url,data.shortcode_media.owner.username,"reels",timestamp,type,reelsPath);
  765. }
  766. }
  767. else{
  768. if(isPreview){
  769. openNewTab(data.shortcode_media.display_resources.at(-1).src);
  770. }
  771. else{
  772. let type = 'jpg';
  773. saveFiles(data.shortcode_media.display_resources.at(-1).src,data.shortcode_media.owner.username,"reels",timestamp,type,reelsPath);
  774. }
  775. }
  776. }
  777. else{
  778. //$('.IG_REELS_THUMBNAIL, .IG_REELS').remove();
  779. var timer = setInterval(()=>{
  780. if($('section > main[role="main"] > div div.x1qjc9v5 video').length > 0){
  781. clearInterval(timer);
  782.  
  783. if(USER_SETTING.SCROLL_BUTTON){
  784. $('#scrollWrapper').remove();
  785. $('section > main[role="main"]').append('<section id="scrollWrapper"></section>');
  786. $('section > main[role="main"] > #scrollWrapper').append('<div class="button-up"><div></div></div>');
  787. $('section > main[role="main"] > #scrollWrapper').append('<div class="button-down"><div></div></div>');
  788.  
  789. $('section > main[role="main"] > #scrollWrapper > .button-up').on('click',function(){
  790. $('section > main[role="main"] > div')[0].scrollBy({top: -30, behavior: "smooth"});
  791. });
  792. $('section > main[role="main"] > #scrollWrapper > .button-down').on('click',function(){
  793. $('section > main[role="main"] > div')[0].scrollBy({top: 30, behavior: "smooth"});
  794. });
  795. }
  796.  
  797. $('section > main[role="main"] > div').children('div').each(function(){
  798. if($(this).children().length > 0){
  799. if(!$(this).children().find('.IG_REELS').length){
  800. $(this).children().css('position','relative');
  801.  
  802. $(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>`);
  803. $(this).children().append(`<div title="${_i18n("NEW_TAB")}" class="IG_REELSNEWTAB"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg></div>`);
  804. $(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>`);
  805.  
  806. // Disable video autoplay
  807. if(USER_SETTING.DISABLE_VIDEO_LOOPING){
  808. $(this).find('video').each(function(){
  809. if(!$(this).data('loop')){
  810. console.log('(reel) Added video event listener #loop');
  811. $(this).on('ended',function(){
  812. $(this).attr('data-loop', true);
  813. let $element = $(this).next().find('div[role="presentation"] > div > div:last-child');
  814.  
  815. if($element.length > 0){
  816. $element.click();
  817. console.log('paused click()');
  818. }
  819. else{
  820. $(this).parent().find('.xpgaw4o').removeAttr('style');
  821. this.pause();
  822. console.log('paused pause()');
  823. }
  824. });
  825. }
  826. });
  827. }
  828. // Modify Video Volume
  829. if(USER_SETTING.MODIFY_VIDEO_VOLUME){
  830. $(this).find('video').each(function(){
  831. if(!$(this).data('modify')){
  832. console.log('(reel) Added video event listener #modify');
  833. this.volume = VIDEO_VOLUME;
  834.  
  835. $(this).on('play',function(){
  836. this.volume = VIDEO_VOLUME;
  837. });
  838. $(this).on('playing',function(){
  839. this.volume = VIDEO_VOLUME;
  840. });
  841.  
  842. $(this).attr('data-modify', true);
  843. }
  844. });
  845. }
  846. }
  847. }
  848. });
  849. }
  850. },250);
  851. }
  852. }
  853.  
  854. /**
  855. * getHighlightStories
  856. * Get a list of all stories in highlight Id.
  857. *
  858. * @param {Integer} highlightId
  859. * @return {Object}
  860. */
  861. function getHighlightStories(highlightId){
  862. return new Promise((resolve,reject)=>{
  863. 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`;
  864.  
  865. GM_xmlhttpRequest({
  866. method: "GET",
  867. url: getURL,
  868. onload: function(response) {
  869. let obj = JSON.parse(response.response);
  870. resolve(obj);
  871. },
  872. onerror: function(err){
  873. reject(err);
  874. }
  875. });
  876. });
  877. }
  878.  
  879. /**
  880. * getStories
  881. * Get a list of all stories in user Id.
  882. *
  883. * @param {Integer} userId
  884. * @return {Object}
  885. */
  886. function getStories(userId){
  887. return new Promise((resolve,reject)=>{
  888. 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`;
  889.  
  890. GM_xmlhttpRequest({
  891. method: "GET",
  892. url: getURL,
  893. onload: function(response) {
  894. let obj = JSON.parse(response.response);
  895. resolve(obj);
  896. },
  897. onerror: function(err){
  898. reject(err);
  899. }
  900. });
  901. });
  902. }
  903.  
  904. /**
  905. * getUserId
  906. * Get user's id with username
  907. *
  908. * @param {String} username
  909. * @return {Integer}
  910. */
  911. function getUserId(username){
  912. return new Promise((resolve,reject)=>{
  913. let getURL = `https://www.instagram.com/web/search/topsearch/?query=${username}`;
  914.  
  915. GM_xmlhttpRequest({
  916. method: "GET",
  917. url: getURL,
  918. onload: function(response) {
  919. // Fix search issue by Discord: sno_w_
  920. let obj = JSON.parse(response.response);
  921. let result = null;
  922. obj.users.forEach(pos => {
  923. if(pos.user.username === username){
  924. result = pos;
  925. }
  926. });
  927.  
  928. if(result != null){
  929. resolve(result);
  930. }
  931. else{
  932. alert("Can not find user info from getUserId()");
  933. }
  934. },
  935. onerror: function(err){
  936. reject(err);
  937. }
  938. });
  939. });
  940. }
  941.  
  942. /**
  943. * getUserHighSizeProfile
  944. * Get user's high quality avatar image.
  945. *
  946. * @param {Integer} userId
  947. * @return {String}
  948. */
  949. function getUserHighSizeProfile(userId){
  950. return new Promise((resolve,reject)=>{
  951. let getURL = `https://i.instagram.com/api/v1/users/${userId}/info/`;
  952.  
  953. GM_xmlhttpRequest({
  954. method: "GET",
  955. url: getURL,
  956. headers: {
  957. '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'
  958. },
  959. onload: function(response) {
  960. let obj = JSON.parse(response.response);
  961. if(obj.status !== 'ok'){
  962. reject('faild');
  963. }
  964. else{
  965. resolve(obj.user.hd_profile_pic_url_info?.url);
  966. }
  967. },
  968. onerror: function(err){
  969. reject(err);
  970. }
  971. });
  972. });
  973. }
  974.  
  975. /**
  976. * getPostOwner
  977. * Get post's author with post shortcode
  978. *
  979. * @param {String} postPath
  980. * @return {String}
  981. */
  982. function getPostOwner(postPath){
  983. return new Promise((resolve,reject)=>{
  984. if(!postPath) reject("NOPATH");
  985. let postShortCode = postPath;
  986. let getURL = `https://www.instagram.com/graphql/query/?query_hash=2c4c2e343a8f64c625ba02b2aa12c7f8&variables=%7B%22shortcode%22:%22${postShortCode}%22}`;
  987.  
  988. GM_xmlhttpRequest({
  989. method: "GET",
  990. url: getURL,
  991. onload: function(response) {
  992. let obj = JSON.parse(response.response);
  993. resolve(obj.data.shortcode_media.owner.username);
  994. },
  995. onerror: function(err){
  996. reject(err);
  997. }
  998. });
  999. });
  1000. }
  1001.  
  1002. /**
  1003. * getBlobMedia
  1004. * Get list of all media files in post with post shortcode
  1005. *
  1006. * @param {String} postPath
  1007. * @return {Object}
  1008. */
  1009. function getBlobMedia(postPath){
  1010. return new Promise((resolve,reject)=>{
  1011. if(!postPath) reject("NOPATH");
  1012. let postShortCode = postPath;
  1013. let getURL = `https://www.instagram.com/graphql/query/?query_hash=2c4c2e343a8f64c625ba02b2aa12c7f8&variables=%7B%22shortcode%22:%22${postShortCode}%22}`;
  1014.  
  1015. GM_xmlhttpRequest({
  1016. method: "GET",
  1017. url: getURL,
  1018. onload: function(response) {
  1019. let obj = JSON.parse(response.response);
  1020. console.log(obj);
  1021. resolve(obj.data);
  1022. },
  1023. onerror: function(err){
  1024. reject(err);
  1025. }
  1026. });
  1027. });
  1028. }
  1029.  
  1030. /**
  1031. * onReadyMyDW
  1032. * Create an event entry point for the download button for the post
  1033. *
  1034. * @param {Boolean} NoDialog - Check if it not showing the dialog
  1035. * @return {void}
  1036. */
  1037. function onReadyMyDW(NoDialog){
  1038. // Whether is Instagram dialog?
  1039. /*
  1040. if(!NoDialog){
  1041. // Running if it is dialog
  1042. $('article, main > div > div.xdt5ytf > div').each(function(){
  1043. $(this).removeAttr('data-snig');
  1044. $(this).unbind('click');
  1045. });
  1046. $('.SNKMS_IG_DW_MAIN,.SNKMS_IG_DW_MAIN_VIDEO').remove();
  1047. }
  1048. */
  1049.  
  1050. if(NoDialog == false){
  1051. var repeat = setInterval(() => {
  1052. // div.xdt5ytf << (sigle post in top, not floating) >>
  1053. 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);
  1054. createDownloadButton();
  1055. },5);
  1056. }
  1057. else{
  1058. createDownloadButton();
  1059. }
  1060. }
  1061.  
  1062. /**
  1063. * getAppID
  1064. * Get Instagram App ID
  1065. *
  1066. * @return {?integer}
  1067. */
  1068. function getAppID(){
  1069. let result = null;
  1070. $('script[type="application/json"]').each(function(){
  1071. const regexp = /"APP_ID":"([0-9]+)"/ig;
  1072. const matcher = $(this).text().match(regexp);
  1073. if(matcher != null && result == null){
  1074. result = [...$(this).text().matchAll(regexp)];
  1075. }
  1076. })
  1077.  
  1078. return (result)?result.at(0).at(-1):null;
  1079. }
  1080.  
  1081. /**
  1082. * getMediaInfo
  1083. * Get Instagram Media object
  1084. *
  1085. * @param {String} mediaId
  1086. * @return {Object}
  1087. */
  1088. function getMediaInfo(mediaId){
  1089. return new Promise((resolve,reject)=>{
  1090. let getURL = `https://i.instagram.com/api/v1/media/${mediaId}/info/`;
  1091.  
  1092. GM_xmlhttpRequest({
  1093. method: "GET",
  1094. url: getURL,
  1095. headers: {
  1096. "User-Agent": window.navigator.userAgent,
  1097. "Accept": "*/*",
  1098. 'X-IG-App-ID': getAppID()
  1099. },
  1100. onload: function(response) {
  1101. let obj = JSON.parse(response.response);
  1102. resolve(obj);
  1103. },
  1104. onerror: function(err){
  1105. reject(err);
  1106. }
  1107. });
  1108. });
  1109. }
  1110.  
  1111. function getVisableNodeIndex($main){
  1112. var index = 0;
  1113. // homepage classList
  1114. var $dot = $main.find('.x1iyjqo2 > div > div:last-child > div');
  1115.  
  1116. // dialog classList, main top classList
  1117. if($dot == null || !$dot.hasClass('_acnb')){
  1118. $dot = $main.find('._aatk > div > div:last-child').eq(0).children('div');
  1119. }
  1120.  
  1121. $dot.filter('._acnb').each(function(sIndex){
  1122. if($(this).hasClass('_acnf')){
  1123. index = sIndex;
  1124. }
  1125. });
  1126.  
  1127. return index;
  1128. }
  1129.  
  1130. /**
  1131. * createDownloadButton
  1132. * Create a download button in the upper right corner of each post
  1133. *
  1134. * @return {void}
  1135. */
  1136. function createDownloadButton(){
  1137. // Add download icon per each posts
  1138. $('article, section:visible > main > div > div.xdt5ytf, div._aap0[role="presentation"]').each(function(index){
  1139. // If it is have not download icon
  1140. // class x1iyjqo2 mean user profile pages post list container
  1141. if(!$(this).attr('data-snig') && !$(this).hasClass('x1iyjqo2') && !$(this).children('article')?.hasClass('x1iyjqo2')){
  1142. console.log("Found article");
  1143. var rightPos = 15;
  1144. var topPos = 15;
  1145. var $mainElement = $(this);
  1146.  
  1147. // not loop each in single top post
  1148. if(this.tagName === "DIV" && index != 0){
  1149. return;
  1150. }
  1151.  
  1152. // New post UI by Discord: ken
  1153. if(this.tagName === "DIV" && $(this).attr('role') === "presentation"){
  1154. rightPos = 28;
  1155. topPos = 75;
  1156. $mainElement = $('div._aap0[role="presentation"]').parents('div._aamm').parent().parent().parent().parent().parent();
  1157. }
  1158.  
  1159.  
  1160. // Add the download icon
  1161. let $childElement = $mainElement.children("div").children("div");
  1162. $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>`);
  1163. $childElement.eq((this.tagName === "DIV")? 0 : $childElement.length - 2).append(`<div title="${_i18n("NEW_TAB")}" class="SNKMS_IG_NEWTAB_MAIN" style="right:${rightPos + 35}px;top:${topPos}px;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg></div>`);
  1164. $childElement.css('position','relative');
  1165.  
  1166. // Disable video autoplay
  1167. if(USER_SETTING.DISABLE_VIDEO_LOOPING){
  1168. $(this).find('video').each(function(){
  1169. if(!$(this).data('loop')){
  1170. console.log('(post) Added video event listener #loop');
  1171. $(this).on('ended',function(){
  1172. $(this).attr('data-loop', true);
  1173. this.pause();
  1174. });
  1175. }
  1176. });
  1177. }
  1178.  
  1179. // Modify Video Volume
  1180. if(USER_SETTING.MODIFY_VIDEO_VOLUME){
  1181. $(this).find('video').each(function(){
  1182. if(!$(this).data('modify')){
  1183. console.log('(post) Added video event listener #modify');
  1184. this.volume = VIDEO_VOLUME;
  1185.  
  1186. $(this).on('play',function(){
  1187. this.volume = VIDEO_VOLUME;
  1188. });
  1189. $(this).on('playing',function(){
  1190. this.volume = VIDEO_VOLUME;
  1191. });
  1192.  
  1193. $(this).attr('data-modify', true);
  1194. }
  1195. });
  1196. }
  1197.  
  1198. $(this).on('click', '.SNKMS_IG_NEWTAB_MAIN', function(e){
  1199. GL_username = $(this).parent().parent().parent().attr('data-username');
  1200. GL_postPath = location.pathname.replace(/\/$/,'').split('/').at(-1) || $(this).parent().parent().parent().find('a[href^="/p/"]').first().attr("href").split("/").at(2) || $(this).parent().parent().children("div:last-child").children("div").children("div:last-child").find('a[href^="/p/"]').last().attr("href").split("/").at(2);
  1201.  
  1202. var $main = $(this).parent().parent().parent();
  1203. var index = getVisableNodeIndex($main);
  1204.  
  1205. IG_createDM(true, false);
  1206.  
  1207. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY", "");
  1208. let checkBlob = setInterval(()=>{
  1209. if($('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').length > 0){
  1210. clearInterval(checkBlob);
  1211. var href = $('.IG_SN_DIG .IG_SN_DIG_BODY a[data-globalindex="'+(index+1)+'"]')?.attr('data-href');
  1212.  
  1213. if(href){
  1214. openNewTab(href);
  1215. }
  1216. else{
  1217. alert('Can not find open tab url.');
  1218. }
  1219.  
  1220. $('.IG_SN_DIG').remove();
  1221. }
  1222. },250);
  1223. });
  1224. // Running if user click the download icon
  1225. $(this).on('click','.SNKMS_IG_DW_MAIN', async function(e){
  1226. GL_username = $(this).parent().parent().parent().attr('data-username');
  1227. GL_postPath = location.pathname.replace(/\/$/,'').split('/').at(-1) || $(this).parent().parent().parent().find('a[href^="/p/"]').first().attr("href").split("/").at(2) || $(this).parent().parent().children("div:last-child").children("div").children("div:last-child").find('a[href^="/p/"]').last().attr("href").split("/").at(2);
  1228.  
  1229. // Create element that download dailog
  1230. IG_createDM(USER_SETTING.DIRECT_DOWNLOAD_ALL, true);
  1231.  
  1232. $("#article-id").html(`<a href="https://www.instagram.com/p/${GL_postPath}">${GL_postPath}</a>`);
  1233.  
  1234. if(USER_SETTING.DIRECT_DOWNLOAD_VISABLE_RESOURCE){
  1235. IG_setDM(true);
  1236.  
  1237. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY", "");
  1238.  
  1239. var index = getVisableNodeIndex($(this).parent().parent().parent());
  1240. let checkBlob = setInterval(()=>{
  1241. if($('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').length > 0){
  1242. clearInterval(checkBlob);
  1243. var href = $('.IG_SN_DIG .IG_SN_DIG_BODY a[data-globalindex="'+(index+1)+'"]')?.attr('data-href');
  1244.  
  1245. if(href){
  1246. $('.IG_SN_DIG .IG_SN_DIG_BODY a[data-globalindex="'+(index+1)+'"]')?.click();
  1247. }
  1248. else{
  1249. alert('Can not find download url.');
  1250. }
  1251.  
  1252. $('.IG_SN_DIG').remove();
  1253. }
  1254. },250);
  1255.  
  1256. return;
  1257. }
  1258.  
  1259. if(!USER_SETTING.DIRECT_DOWNLOAD_ALL){
  1260. // Find video/image element and add the download icon
  1261. var s = 0;
  1262. var multiple = $(this).parent().parent().find('._aap0 ._acaz').length;
  1263. var pathname = window.location.pathname;
  1264. var fullpathname = "/"+pathname.split('/')[1]+"/"+pathname.split('/')[2]+"/";
  1265. var blob = USER_SETTING.FORCE_FETCH_ALL_RESOURCES;
  1266.  
  1267. // If posts have more than one images or videos.
  1268. if(multiple){
  1269. $(this).parent().find('._aap0 ._acaz').each(function(){
  1270. let element_videos = $(this).parent().parent().find('video');
  1271. //if(element_videos && element_videos.attr('src') && element_videos.attr('src').match(/^blob:/ig)){
  1272. if(element_videos && element_videos.attr('src')){
  1273. blob = true;
  1274. }
  1275. });
  1276.  
  1277.  
  1278. if(blob || USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  1279. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_MULTIPLE"));
  1280. }
  1281. else{
  1282. $(this).parent().find('._aap0 ._acaz').each(function(){
  1283. s++;
  1284. let element_videos = $(this).find('video');
  1285. let element_images = $(this).find('._aagv img');
  1286. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  1287.  
  1288. if(element_videos && element_videos.attr('src')){
  1289. blob = true;
  1290. }
  1291. if(element_images && imgLink){
  1292. $('.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>`);
  1293. }
  1294.  
  1295. });
  1296.  
  1297. if(blob){
  1298. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_RELOAD"));
  1299. }
  1300. }
  1301. }
  1302. else{
  1303. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  1304. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_MULTIPLE"));
  1305. }
  1306. else{
  1307. s++;
  1308. let element_videos = $(this).parent().parent().find('video');
  1309. let element_images = $(this).parent().parent().find('._aagv img');
  1310. let imgLink = (element_images.attr('srcset'))?element_images.attr('srcset').split(" ")[0]:element_images.attr('src');
  1311.  
  1312.  
  1313. if(element_videos && element_videos.attr('src')){
  1314. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_ONE"));
  1315. }
  1316. if(element_images && imgLink){
  1317. $('.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>`);
  1318. }
  1319. }
  1320. }
  1321. }
  1322.  
  1323. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1324. $(this).wrap('<div></div>');
  1325. $(this).before('<label class="inner_box_wrapper"><input class="inner_box" type="checkbox"><span></span></label>');
  1326. $(this).after('<svg class="newTab" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg>');
  1327.  
  1328. if($(this).attr('data-name') == 'video'){
  1329. $(this).after('<svg class="videoThumbnail" width="24" height="24" 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>');
  1330. }
  1331. });
  1332.  
  1333. if(USER_SETTING.DIRECT_DOWNLOAD_ALL){
  1334. createMediaListDOM(GL_postPath,".IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY",_i18n("LOAD_BLOB_MULTIPLE"));
  1335. let checkBlob = setInterval(()=>{
  1336. if($('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').length > 0){
  1337. clearInterval(checkBlob);
  1338. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1339. $(this).click();
  1340. });
  1341.  
  1342. $('.IG_SN_DIG').remove();
  1343. }
  1344. },250);
  1345. }
  1346. });
  1347.  
  1348. // Add the mark that download is ready
  1349. var username = $(this).find("header > div:last-child > div:first-child span a").first().text();
  1350.  
  1351. $(this).attr('data-snig','canDownload');
  1352. $(this).attr('data-username',username);
  1353. }
  1354. });
  1355. }
  1356.  
  1357. /**
  1358. * createMediaListDOM
  1359. * Create a list of media elements from post URLs
  1360. *
  1361. * @param {String} postURL
  1362. * @param {String} selector - Use CSS element selectors to choose where it appears.
  1363. * @param {String} message - i18n display loading message
  1364. * @return {void}
  1365. */
  1366. async function createMediaListDOM(postURL,selector,message){
  1367. $(`${selector} a`).remove();
  1368. $(selector).append('<p id="_SNLOAD">'+ message +'</p>');
  1369. let media = await getBlobMedia(postURL);
  1370.  
  1371. let idx = 1;
  1372. let resource = media.shortcode_media;
  1373.  
  1374. // GraphVideo
  1375. if(resource.__typename == "GraphVideo" && resource.video_url){
  1376. $(selector).append(`<a media-id="${resource.id}" 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>`);
  1377. idx++;
  1378. }
  1379. // GraphImage
  1380. if(resource.__typename == "GraphImage"){
  1381. $(selector).append(`<a media-id="${resource.id}" 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>`);
  1382. idx++;
  1383. }
  1384. // GraphSidecar
  1385. if(resource.__typename == "GraphSidecar" && resource.edge_sidecar_to_children){
  1386. for(let e of resource.edge_sidecar_to_children.edges){
  1387. if(e.node.__typename == "GraphVideo"){
  1388. $(selector).append(`<a media-id="${e.node.id}" 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>`);
  1389. }
  1390.  
  1391. if(e.node.__typename == "GraphImage"){
  1392. $(selector).append(`<a media-id="${e.node.id}" 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>`);
  1393. }
  1394. idx++;
  1395. }
  1396. }
  1397.  
  1398. $("#_SNLOAD").remove();
  1399. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_BODY a').each(function(){
  1400. $(this).wrap('<div></div>');
  1401. $(this).before('<label class="inner_box_wrapper"><input class="inner_box" type="checkbox"><span></span></label>');
  1402. $(this).after('<svg class="newTab" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M20 14a1 1 0 0 0-1 1v3.077c0 .459-.022.57-.082.684a.363.363 0 0 1-.157.157c-.113.06-.225.082-.684.082H5.923c-.459 0-.571-.022-.684-.082a.363.363 0 0 1-.157-.157c-.06-.113-.082-.225-.082-.684L4.999 5.5a.5.5 0 0 1 .5-.5l3.5.005a1 1 0 1 0 .002-2L5.501 3a2.5 2.5 0 0 0-2.502 2.5v12.577c0 .76.083 1.185.32 1.627.223.419.558.753.977.977.442.237.866.319 1.627.319h12.154c.76 0 1.185-.082 1.627-.319.419-.224.753-.558.977-.977.237-.442.319-.866.319-1.627V15a1 1 0 0 0-1-1zm-2-9.055v-.291l-.39.09A10 10 0 0 1 15.36 5H14a1 1 0 1 1 0-2l5.5.003a1.5 1.5 0 0 1 1.5 1.5V10a1 1 0 1 1-2 0V8.639c0-.757.086-1.511.256-2.249l.09-.39h-.295a10 10 0 0 1-1.411 1.775l-5.933 5.932a1 1 0 0 1-1.414-1.414l5.944-5.944A10 10 0 0 1 18 4.945z" fill="currentColor"/></svg>');
  1403.  
  1404. if($(this).attr('data-name') == 'video'){
  1405. $(this).after('<svg class="videoThumbnail" width="24" height="24" 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>');
  1406. }
  1407. });
  1408. }
  1409.  
  1410. /**
  1411. * IG_createDM
  1412. * A dialog showing a list of all media files in the post
  1413. *
  1414. * @param {Boolean} hasHidden
  1415. * @param {Boolean} hasCheckbox
  1416. * @return {void}
  1417. */
  1418. function IG_createDM(hasHidden, hasCheckbox){
  1419. let isHidden = (hasHidden)?"hidden":"";
  1420. $('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>');
  1421. $('.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>');
  1422.  
  1423. if(hasCheckbox){
  1424. $('.IG_SN_DIG .IG_SN_DIG_MAIN .IG_SN_DIG_TITLE').append(`<div style="text-align: center;" id="button_group"></div>`);
  1425. $('.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>`);
  1426. $('.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>`);
  1427. $('.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>`);
  1428. }
  1429. }
  1430.  
  1431. /**
  1432. * IG_setDM
  1433. * Set a dialog status
  1434. *
  1435. * @param {Boolean} hasHidden
  1436. * @return {void}
  1437. */
  1438. function IG_setDM(hasHidden){
  1439. if($('.IG_SN_DIG').length){
  1440. if(hasHidden){
  1441. $('.IG_SN_DIG').addClass("hidden");
  1442. }
  1443. else{
  1444. $('.IG_SN_DIG').removeClass("hidden");
  1445. }
  1446. }
  1447. }
  1448.  
  1449. /**
  1450. * saveFiles
  1451. * Download the specified media URL to the computer
  1452. *
  1453. * @param {String} downloadLink
  1454. * @param {String} username
  1455. * @param {String} sourceType
  1456. * @param {Integer} timestamp
  1457. * @param {String} filetype
  1458. * @param {String} shortcode
  1459. * @return {void}
  1460. */
  1461. function saveFiles(downloadLink,username,sourceType,timestamp,filetype,shortcode){
  1462. $('div[id^="mount"] > div > div > div:first').removeClass('x1s85apg');
  1463. $('div[id^="mount"] > div > div > div:first').css('z-index','20000');
  1464. fetch(downloadLink).then(res => {
  1465. return res.blob().then(dwel => {
  1466. $('div[id^="mount"] > div > div > div:first').addClass('x1s85apg');
  1467. $('div[id^="mount"] > div > div > div:first').css('z-index','');
  1468. createSaveFileElement(downloadLink,dwel,username,sourceType,timestamp,filetype,shortcode);
  1469. });
  1470. });
  1471. }
  1472.  
  1473. /**
  1474. * createSaveFileElement
  1475. * Download the specified media with link element
  1476. *
  1477. * @param {String} downloadLink
  1478. * @param {Object} object
  1479. * @param {String} username
  1480. * @param {String} sourceType
  1481. * @param {Integer} timestamp
  1482. * @param {String} filetype
  1483. * @param {String} shortcode
  1484. * @return {void}
  1485. */
  1486. function createSaveFileElement(downloadLink,object,username,sourceType,timestamp,filetype,shortcode) {
  1487. const a = document.createElement("a");
  1488. const name = username+'-'+sourceType+'-'+((USER_SETTING.RENAME_SHORTCODE && shortcode)?shortcode+'-':'')+timestamp+'.'+filetype;
  1489. const originally = username + '_' + new URL(downloadLink).pathname.split('/').at(-1).split('.').slice(0,-1).join('.') + '.' + filetype;
  1490.  
  1491. a.href = URL.createObjectURL(object);
  1492. a.setAttribute("download", (USER_SETTING.AUTO_RENAME)?name:originally);
  1493. a.click();
  1494. a.remove();
  1495. }
  1496.  
  1497. /**
  1498. * triggerLinkElement
  1499. * Trigger the link element to start downloading the resource
  1500. *
  1501. * @param {Object} element
  1502. * @return {void}
  1503. */
  1504. async function triggerLinkElement(element) {
  1505. let date = new Date().getTime();
  1506. let timestamp = Math.floor(date / 1000);
  1507. let username = ($(element).attr('data-username')) ? $(element).attr('data-username') : GL_username;
  1508.  
  1509. if(!username && $(element).attr('data-path')){
  1510. console.log('catching owner name from shortcode:',$(element).attr('data-href'));
  1511. username = await getPostOwner($(element).attr('data-path'));
  1512. }
  1513.  
  1514. if(USER_SETTING.FORCE_RESOURCE_VIA_MEDIA){
  1515. let result = await getMediaInfo($(element).attr('media-id'));
  1516.  
  1517. if(result.status === 'ok'){
  1518. if(result.items[0].video_versions){
  1519. saveFiles(result.items[0].video_versions[0].url, username, $(element).attr('data-name'),timestamp, $(element).attr('data-type'), $(element).attr('data-path'));
  1520. }
  1521. else{
  1522. saveFiles(result.items[0].image_versions2.candidates[0].url, username, $(element).attr('data-name'),timestamp, $(element).attr('data-type'), $(element).attr('data-path'));
  1523. }
  1524. }
  1525. else{
  1526. alert('fetch failed from Media API, ' + result.message);
  1527. console.log(result);
  1528. }
  1529. }
  1530. else{
  1531. saveFiles($(element).attr('data-href'),username,$(element).attr('data-name'),timestamp,$(element).attr('data-type'), $(element).attr('data-path'));
  1532. }
  1533. }
  1534.  
  1535. /**
  1536. * translateText
  1537. * i18n translation text
  1538. *
  1539. * @param {String} lang
  1540. * @return {void}
  1541. */
  1542. function translateText(lang){
  1543. return {
  1544. "zh-TW": {
  1545. "NEW_TAB": "在新分頁中開啟",
  1546. "SHOW_DOM_TREE": "顯示 DOM Tree",
  1547. "SELECT_AND_COPY": "全選並複製輸入框的內容",
  1548. "DOWNLOAD_DOM_TREE": "將 DOM Tree 下載為文字文件",
  1549. "REPORT_GITHUB": "在 GitHub 上回報問題",
  1550. "REPORT_DISCORD": "在 Discord 支援伺服器上回報問題",
  1551. "DEBUG": "偵錯視窗",
  1552. "CLOSE": "關閉",
  1553. "ALL_CHECK": "全選",
  1554. "BATCH_DOWNLOAD_SELECTED": "批次下載已勾選資源",
  1555. "BATCH_DOWNLOAD_DIRECT": "批次下載全部資源",
  1556. "IMG": "相片",
  1557. "VID": "影片",
  1558. "DDL": "快速下載",
  1559. "DDL_INTRO": "勾選後將直接下載點選當下位置的相片/影片",
  1560. "DW": "下載",
  1561. "THUMBNAIL_INTRO": "下載影片縮圖",
  1562. "LOAD_BLOB_ONE": "正在載入二進位大型物件...",
  1563. "LOAD_BLOB_MULTIPLE": "正在載入多個二進位大型物件...",
  1564. "LOAD_BLOB_RELOAD": "正在重新載入二進位大型物件...",
  1565. "NO_CHECK_RESOURCE": "您需要勾選資源才能下載。",
  1566. "NO_VID_URL": "找不到影片網址",
  1567. "SETTING": "設定",
  1568. "AUTO_RENAME": "自動重新命名檔案",
  1569. "RENAME_SHORTCODE": "重新命名檔案並包含 Shortcode",
  1570. "DISABLE_VIDEO_LOOPING": "關閉影片自動循環播放",
  1571. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "右鍵點擊使用者限時動態區域頭貼時重定向",
  1572. "FORCE_FETCH_ALL_RESOURCES": "強制提取貼文中所有資源",
  1573. "DIRECT_DOWNLOAD_VISABLE_RESOURCE": "直接下載貼文中的可見資源",
  1574. "DIRECT_DOWNLOAD_ALL": "直接下載貼文中的所有資源",
  1575. "MODIFY_VIDEO_VOLUME": "修改影片音量(右鍵設定)",
  1576. "SCROLL_BUTTON": "為連續短片頁面啟用捲動按鈕",
  1577. "FORCE_RESOURCE_VIA_MEDIA": "透過 Media API 強制提取資源",
  1578. "AUTO_RENAME_INTRO": "將檔案自動重新命名為以下格式:\n使用者名稱-類型-時間戳.檔案類型\n例如:instagram-photo-1670350000.jpg\n\n若設為 false,則檔案名稱將保持原始樣貌。 \n例如:instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1579. "RENAME_SHORTCODE_INTRO": "將檔案自動重新命名為以下格式:\n使用者名稱-類型-Shortcode-時間戳.檔案類型\n示例:instagram-photo-CwkxyiVynpW-1670350000.jpg\n\n此功能僅在[自動重新命名檔案]設定為 TRUE 時有效。",
  1580. "DISABLE_VIDEO_LOOPING_INTRO": "關閉連續短片和貼文中影片自動循環播放。",
  1581. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "右鍵點選首頁限時動態區域中的使用者頭貼時,重新導向到使用者的個人資料頁面。",
  1582. "FORCE_FETCH_ALL_RESOURCES_INTRO": "透過 Instagram API 強制取得貼文中的所有資源(照片和影片),以取消每個貼文單次提取三個資源的限制。",
  1583. "DIRECT_DOWNLOAD_VISABLE_RESOURCE_INTRO": "直接下載貼文中的目前資源。",
  1584. "DIRECT_DOWNLOAD_ALL_INTRO": "按下下載按鈕時將直接強制提取貼文中的所有資源並下載。",
  1585. "MODIFY_VIDEO_VOLUME_INTRO": "修改連續短片和貼文的影片播放音量(右鍵可開啟音量設定條)。",
  1586. "SCROLL_BUTTON_INTRO": "為連續短片頁面的右下角啟用上下捲動按鈕。",
  1587. "FORCE_RESOURCE_VIA_MEDIA_INTRO": "Media API 將嘗試獲取盡可能最高品質的照片或影片,但加載時間會更長。"
  1588. },
  1589. "zh-CN": {
  1590. "NEW_TAB": "在新选项卡中打开",
  1591. "SHOW_DOM_TREE": "显示 DOM Tree",
  1592. "SELECT_AND_COPY": "全选并复制输入框的内容",
  1593. "DOWNLOAD_DOM_TREE": "将 DOM Tree 下载为文本文件",
  1594. "REPORT_GITHUB": "在 GitHub 上报告问题",
  1595. "REPORT_DISCORD": "在 Discord 支援服务器上报告问题",
  1596. "DEBUG": "调试窗口",
  1597. "CLOSE": "关闭",
  1598. "ALL_CHECK": "全选",
  1599. "BATCH_DOWNLOAD_SELECTED": "批量下载已勾选资源",
  1600. "BATCH_DOWNLOAD_DIRECT": "批量下载全部资源",
  1601. "IMG": "图像",
  1602. "VID": "视频",
  1603. "DDL": "便捷下载",
  1604. "DDL_INTRO": "勾选后将直接下载點擊當下位置的图像/视频",
  1605. "DW": "下载",
  1606. "THUMBNAIL_INTRO": "下载视频缩略图",
  1607. "LOAD_BLOB_ONE": "正在载入大型媒体对象...",
  1608. "LOAD_BLOB_MULTIPLE": "正在载入多个大型媒体对象...",
  1609. "LOAD_BLOB_RELOAD": "正在重新载入大型媒体对象...",
  1610. "NO_CHECK_RESOURCE": "您需要勾選资源才能下載。",
  1611. "NO_VID_URL": "找不到视频网址",
  1612. "SETTING": "设置",
  1613. "AUTO_RENAME": "自动重命名文件",
  1614. "RENAME_SHORTCODE": "重命名文件并包含物件短码",
  1615. "DISABLE_VIDEO_LOOPING": "禁用视频自动循环",
  1616. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "右键单击用户故事区域头像时重定向",
  1617. "FORCE_FETCH_ALL_RESOURCES": "强制抓取帖子中所有资源",
  1618. "DIRECT_DOWNLOAD_VISABLE_RESOURCE": "直接下载帖子中的可见资源",
  1619. "DIRECT_DOWNLOAD_ALL": "直接下载帖子中的所有资源",
  1620. "MODIFY_VIDEO_VOLUME": "修改视频音量(右击设置)",
  1621. "SCROLL_BUTTON": "为 Reels 页面启用卷动按钮",
  1622. "FORCE_RESOURCE_VIA_MEDIA": "通过 Media API 强制获取资源",
  1623. "AUTO_RENAME_INTRO": "将文件自动重新命名为以下格式类型:\n用户名-类型-时间戳.文件类型\n例如:instagram-photo-1670350000.jpg\n\n若设为false,则文件名将保持原样。 \n例如:instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1624. "RENAME_SHORTCODE_INTRO": "自动重命名文件为以下格式类型:\n用户名-类型-短码-时间戳.文件类型\n示例:instagram-photo-CwkxyiVynpW-1670350000.jpg\n\n它仅在[自动重命名文件]设置为 TRUE 时有效。",
  1625. "DISABLE_VIDEO_LOOPING_INTRO": "禁用 Reels 和帖子中的视频自动播放。",
  1626. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "右键单击主页故事区域中的用户头像,重定向到用户的个人资料页面。",
  1627. "FORCE_FETCH_ALL_RESOURCES_INTRO": "通过 Instagram API 强制获取帖子中的所有资源(照片和视频),以取消每个帖子单次抓取三个资源的限制。",
  1628. "DIRECT_DOWNLOAD_VISABLE_RESOURCE_INTRO": "直接下载帖子中的当前资源。",
  1629. "DIRECT_DOWNLOAD_ALL_INTRO": "当您点击下载按钮时,帖子中的所有资源将被直接强制抓取并下载。",
  1630. "MODIFY_VIDEO_VOLUME_INTRO": "修改 Reels 和帖子中的视频播放音量(右击可开启音量设置滑条)。",
  1631. "SCROLL_BUTTON_INTRO": "为 Reels 页面的右下角启用上下卷动按钮。",
  1632. "FORCE_RESOURCE_VIA_MEDIA_INTRO": "Media API 将尝试获取尽可能最高质量的照片或视频,但加载时间会更长。"
  1633. },
  1634. "en-US": {
  1635. "NEW_TAB": "Open in new tab",
  1636. "SHOW_DOM_TREE": "Show DOM Tree",
  1637. "SELECT_AND_COPY": "Select All and Copy of the Input Box",
  1638. "DOWNLOAD_DOM_TREE": "Download DOM Tree as Text File",
  1639. "REPORT_GITHUB": "Report Issue On GitHub",
  1640. "REPORT_DISCORD": "Report Issue On Discord Support Server",
  1641. "DEBUG": "Debug Window",
  1642. "CLOSE": "Close",
  1643. "ALL_CHECK": "Select All",
  1644. "BATCH_DOWNLOAD_SELECTED": "Download Selected Resources",
  1645. "BATCH_DOWNLOAD_DIRECT": "Download All Resources",
  1646. "IMG": "Image",
  1647. "VID": "Video",
  1648. "DDL": "Quick Download",
  1649. "DDL_INTRO": "Checking it will direct download current photo/media in the posts.",
  1650. "DW": "Download",
  1651. "THUMBNAIL_INTRO": "Download video thumbnail",
  1652. "LOAD_BLOB_ONE": "Loading Blob Media...",
  1653. "LOAD_BLOB_MULTIPLE": "Loading Blob Media and others...",
  1654. "LOAD_BLOB_RELOAD": "Detect Blob Media, now reloading...",
  1655. "NO_CHECK_RESOURCE": "You need to check resource to download.",
  1656. "NO_VID_URL": "Can not find video url.",
  1657. "SETTING": "Settings",
  1658. "AUTO_RENAME": "Automatically Rename Files",
  1659. "RENAME_SHORTCODE": "Rename The File and Include Shortcode",
  1660. "DISABLE_VIDEO_LOOPING": "Disable Video Auto-looping",
  1661. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "Redirect When Right-Clicking User Story Picture",
  1662. "FORCE_FETCH_ALL_RESOURCES": "Forcing Fetch All Resources In the Post",
  1663. "DIRECT_DOWNLOAD_VISABLE_RESOURCE": "Directly Download the Visible Resources In the Post",
  1664. "DIRECT_DOWNLOAD_ALL": "Directly Download All Resources In the Post",
  1665. "MODIFY_VIDEO_VOLUME": "Modify Video Volume (Right-Click To Set)",
  1666. "SCROLL_BUTTON": "Enable Scroll Buttons For Reels Page",
  1667. "FORCE_RESOURCE_VIA_MEDIA": "Force Fetch Resource via Media API",
  1668. "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",
  1669. "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.",
  1670. "DISABLE_VIDEO_LOOPING_INTRO": "Disable video auto-looping in reels and posts.",
  1671. "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.",
  1672. "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.",
  1673. "DIRECT_DOWNLOAD_VISABLE_RESOURCE_INTRO": "Directly download the current resources in the post.",
  1674. "DIRECT_DOWNLOAD_ALL_INTRO": "When you click the download button, all resources in the post will be directly forced to be fetched and downloaded.",
  1675. "MODIFY_VIDEO_VOLUME_INTRO": "Modify the video playback volume in Reels and Posts (right-click to open the volume setting slider).",
  1676. "SCROLL_BUTTON_INTRO": "Enable scroll buttons for the lower right corner of Reels page.",
  1677. "FORCE_RESOURCE_VIA_MEDIA_INTRO": "The Media API will try to get the highest quality photo or video possible, but it will take longer to load."
  1678. },
  1679. "ro": {
  1680. "NEW_TAB": "Deschide într-o filă nouă",
  1681. "SHOW_DOM_TREE": "Afișează arborele DOM",
  1682. "SELECT_AND_COPY": "Selectează tot și copiază din caseta de introducere",
  1683. "DOWNLOAD_DOM_TREE": "Descarcă arborele DOM în format text",
  1684. "REPORT_GITHUB": "Raportează o problemă pe GitHub",
  1685. "REPORT_DISCORD": "Raportează o problemă pe serverul de suport Discord",
  1686. "DEBUG": "Fereastră de depanare",
  1687. "CLOSE": "Închide",
  1688. "ALL_CHECK": "Selectează toate",
  1689. "BATCH_DOWNLOAD_SELECTED": "Descarcă resursele selectate",
  1690. "BATCH_DOWNLOAD_DIRECT": "Descarcă toate resursele",
  1691. "IMG": "Imagine",
  1692. "VID": "Videoclip",
  1693. "DDL": "Descărcare rapidă",
  1694. "DDL_INTRO": "Bifând această opțiune, se va descărca direct fotografia/fișierul multimedia actual(ă) din postări.",
  1695. "DW": "Descarcă",
  1696. "THUMBNAIL_INTRO": "Descarcă miniatura videoclipului",
  1697. "LOAD_BLOB_ONE": "Se încarcă conținutul media în format Blob...",
  1698. "LOAD_BLOB_MULTIPLE": "Se încarcă conținutul media în format Blob și celelalte...",
  1699. "LOAD_BLOB_RELOAD": "Se detectează conținutul media în format Blob, se reîncarcă acum...",
  1700. "NO_CHECK_RESOURCE": "Trebuie să bifezi resursa pentru a o descărca.",
  1701. "NO_VID_URL": "Nu se poate găsi URL-ul videoclipului.",
  1702. "SETTING": "Setări",
  1703. "AUTO_RENAME": "Redenumește automat fișierele",
  1704. "RENAME_SHORTCODE": "Redenumește fișierul și include cod scurt",
  1705. "DISABLE_VIDEO_LOOPING": "Dezactivează redarea automată în buclă a videoclipurilor",
  1706. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE": "Redirecționează când dai click dreapta pe fotografia profilului în storyul utilizatorului",
  1707. "FORCE_FETCH_ALL_RESOURCES": "Forțează preluarea tuturor resurselor din postare",
  1708. "DIRECT_DOWNLOAD_VISABLE_RESOURCE": "Descarcă direct resursele vizibile din postare",
  1709. "DIRECT_DOWNLOAD_ALL": "Descarcă direct toate resursele din postare",
  1710. "MODIFY_VIDEO_VOLUME": "Modifică volumul videoclipurilor (Click dreapta pentru a seta)",
  1711. "SCROLL_BUTTON": "Activează butoanele de derulare pentru pagina Reels",
  1712. "FORCE_RESOURCE_VIA_MEDIA": "Forțează preluarea resurselor prin intermediul Media API",
  1713. "AUTO_RENAME_INTRO": "Redenumește automat fișierele cu formatul următor:\nNUME_DE_UTILIZATOR-TIP-MARCAJ_DE_TIMP.TIPUL_FIȘIERULUI\nExemplu: instagram-photo-1670350000.jpg\n\nDacă este setat pe fals, numele fișierului va rămâne neschimbat.\nExemplu: instagram_321565527_679025940443063_4318007696887450953_n.jpg",
  1714. "RENAME_SHORTCODE_INTRO": "Redenumește automat fișierele cu formatul următor:\nNUME_DE_UTILIZATOR-TIP-COD_SCURT-MARCAJ_DE_TIMP.TIPUL_FIȘIERULUI\nExemplu: instagram-photo-CwkxyiVynpW-1670350000.jpg\n\nFuncționează DOAR dacă setarea [Redenumește automat fișierele] este pe ADEVĂRAT.",
  1715. "DISABLE_VIDEO_LOOPING_INTRO": "Dezactivează redarea automată în buclă a videoclipurilor din Reels și Postări.",
  1716. "REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE_INTRO": "Redirecționează către pagina de profil a unui utilizator când faci click dreapta pe avatarul acestuia în zona storyului de pe pagina principală.",
  1717. "FORCE_FETCH_ALL_RESOURCES_INTRO": "Forțează preluarea tuturor resurselor (fotografii și videoclipuri) dintr-o postare prin intermediul API-ului Instagram pentru a elimina limita de trei resurse per postare.",
  1718. "DIRECT_DOWNLOAD_VISABLE_RESOURCE_INTRO": "Descarcă direct resursele actuale din postare.",
  1719. "DIRECT_DOWNLOAD_ALL_INTRO": "Atunci când apeși butonul de descărcare, toate resursele din postare vor fi direct forțate să fie preluate și descărcate.",
  1720. "MODIFY_VIDEO_VOLUME_INTRO": "Modifică volumul redării videoclipurilor în Reels și Postări (click dreapta pentru a deschide cursorul de setare a volumului).",
  1721. "SCROLL_BUTTON_INTRO": "Activează butoanele de derulare pentru colțul din dreapta jos al paginii Reels.",
  1722. "FORCE_RESOURCE_VIA_MEDIA_INTRO": "Media API va încerca să obțină cea mai înaltă calitate posibilă pentru fotografie sau videoclip, dar încărcarea va dura mai mult."
  1723. }
  1724. };
  1725. }
  1726.  
  1727. /**
  1728. * _i18n
  1729. * Perform i18n translation
  1730. *
  1731. * @param {String} text
  1732. * @return {void}
  1733. */
  1734. function _i18n(text){
  1735. let userLang = (lang)?lang:"en-US";
  1736. let translate = {
  1737. "zh-TW": function(){
  1738. return translateText()["zh-TW"];
  1739. },
  1740. "zh-HK": function(){
  1741. return translateText()["zh-TW"];
  1742. },
  1743. "zh-MO": function(){
  1744. return translateText()["zh-TW"];
  1745. },
  1746. "zh-CN": function(){
  1747. return translateText()["zh-CN"];
  1748. },
  1749. "en-US": function(){
  1750. return translateText()["en-US"];
  1751. },
  1752. "ro": function(){
  1753. return translateText()["ro"];
  1754. }
  1755. }
  1756.  
  1757. try{
  1758. return translate[lang]()[text];
  1759. }
  1760. catch{
  1761. return translate["en-US"]()[text];
  1762. }
  1763. }
  1764.  
  1765. /**
  1766. * showSetting
  1767. * Show script settings window
  1768. *
  1769. * @return {void}
  1770. */
  1771. function showSetting(){
  1772. $('.IG_SN_DIG').remove();
  1773. IG_createDM();
  1774. $('.IG_SN_DIG #post_info').text('IG Helper Settings');
  1775.  
  1776.  
  1777. for(let name in USER_SETTING){
  1778. $('.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] === true)?'checked':''}><div class="chbtn"><div class="rounds"></div></div></label>`);
  1779.  
  1780. if(name === 'MODIFY_VIDEO_VOLUME'){
  1781. $('.IG_SN_DIG .IG_SN_DIG_BODY input[id="'+name+'"]').parent('label').on('contextmenu', function(e){
  1782. e.preventDefault();
  1783. if($(this).find('#tempWrapper').length === 0){
  1784. $(this).append('<div id="tempWrapper"></div>');
  1785. $(this).children('#tempWrapper').append('<input value="' + VIDEO_VOLUME + '" type="range" min="0" max="1" step="0.05" />');
  1786. $(this).children('#tempWrapper').append('<input value="' + VIDEO_VOLUME + '" step="0.05" type="number" />');
  1787. $(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));transform: translateY(-50%);top:50%;" 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>');
  1788. }
  1789. });
  1790. }
  1791. }
  1792. }
  1793.  
  1794. /**
  1795. * showDebugDOM
  1796. * Show full DOM tree
  1797. *
  1798. * @return {void}
  1799. */
  1800. function showDebugDOM(){
  1801. $('.IG_SN_DIG').remove();
  1802. IG_createDM();
  1803. $('.IG_SN_DIG #post_info').text('IG Debug DOM Tree');
  1804.  
  1805. $('.IG_SN_DIG .IG_SN_DIG_BODY').append(`<textarea style="width:100%;box-sizing: border-box;height:300px;" readonly></textarea>`);
  1806. $('.IG_SN_DIG .IG_SN_DIG_BODY').append(`<span style="display:block;text-align:center;">`);
  1807. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_DISPLAY_DOM_TREE"><a>${_i18n('SHOW_DOM_TREE')}</a></button>`);
  1808. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_SELECT_DOM_TREE"><a>${_i18n('SELECT_AND_COPY')}</a></button>`);
  1809. $('.IG_SN_DIG .IG_SN_DIG_BODY span').append(`<button class="IG_DOWNLOAD_DOM_TREE"><a>${_i18n('DOWNLOAD_DOM_TREE')}</a></button><br/>`);
  1810. $('.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>`);
  1811. $('.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>`);
  1812. }
  1813.  
  1814. function openNewTab(link){
  1815. var a = document.createElement('a');
  1816. a.href = link;
  1817. a.target = '_blank';
  1818.  
  1819. document.body.appendChild(a);
  1820. a.click();
  1821. a.remove();
  1822. }
  1823.  
  1824. // Running if document is ready
  1825. $(function(){
  1826. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_DISPLAY_DOM_TREE',function(){
  1827. let text = $('div[id^="mount"]')[0];
  1828. $('.IG_SN_DIG .IG_SN_DIG_BODY textarea').text("Location: " + location.pathname + "\nDOM Tree:\n" + text.innerHTML);
  1829. });
  1830.  
  1831. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_SELECT_DOM_TREE',function(){
  1832. $('.IG_SN_DIG .IG_SN_DIG_BODY textarea').select();
  1833. document.execCommand('copy');
  1834. });
  1835.  
  1836. $('body').on('click','.IG_SN_DIG .IG_SN_DIG_BODY .IG_DOWNLOAD_DOM_TREE',function(){
  1837. 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;
  1838. var a = document.createElement("a");
  1839. var file = new Blob([text], {type: "text/plain"});
  1840. a.href = URL.createObjectURL(file);
  1841. a.download = "DOMTree.txt";
  1842.  
  1843. document.body.appendChild(a);
  1844. a.click();
  1845. a.remove();
  1846. });
  1847.  
  1848. // Close the download dialog if user click the close icon
  1849. $('body').on('click','.IG_SN_DIG_BTN,.IG_SN_DIG_BG',function(){
  1850. if($(this).parent('#tempWrapper').length > 0){
  1851. $(this).parent('#tempWrapper').fadeOut(250, function(){
  1852. $(this).remove();
  1853. });
  1854. }
  1855. else{
  1856. $('.IG_SN_DIG').remove();
  1857. }
  1858. });
  1859.  
  1860. $(window).keydown(function(e){
  1861. // Hot key [Alt+Q] to close the download dialog
  1862. if (e.keyCode == '81' && e.altKey){
  1863. $('.IG_SN_DIG').remove();
  1864. e.preventDefault();
  1865. }
  1866. // Hot key [Alt+W] to open the settings dialog
  1867. if (e.keyCode == '87' && e.altKey){
  1868. showSetting();
  1869. e.preventDefault();
  1870. }
  1871.  
  1872. // Hot key [Alt+Z] to open the settings dialog
  1873. if (e.keyCode == '90' && e.altKey){
  1874. showDebugDOM();
  1875. e.preventDefault();
  1876. }
  1877. });
  1878.  
  1879. $('body').on('change', '.IG_SN_DIG input',function(e){
  1880. var name = $(this).attr('id');
  1881.  
  1882. if(name && USER_SETTING[name] !== undefined){
  1883. let isChecked = $(this).prop('checked');
  1884. GM_setValue(name, isChecked);
  1885. USER_SETTING[name] = isChecked;
  1886.  
  1887. console.log('user settings', name, isChecked);
  1888. }
  1889. });
  1890.  
  1891. $('body').on('click', '.IG_SN_DIG .globalSettings',function(e){
  1892. if($(this).find('#tempWrapper').length > 0){
  1893. e.preventDefault();
  1894. }
  1895. });
  1896.  
  1897. $('body').on('change', '.IG_SN_DIG #tempWrapper input',function(){
  1898. let value = $(this).val();
  1899.  
  1900. if($(this).attr('type') == 'range'){
  1901. $(this).next().val(value);
  1902. }
  1903. else{
  1904. $(this).prev().val(value);
  1905. }
  1906.  
  1907. if(value >= 0 && value <= 1){
  1908. VIDEO_VOLUME = value;
  1909. GM_setValue('G_VIDEO_VOLUME', value);
  1910. }
  1911. });
  1912.  
  1913. $('body').on('input', '.IG_SN_DIG #tempWrapper input',function(e){
  1914. if($(this).attr('type') == 'range'){
  1915. let value = $(this).val();
  1916. $(this).next().val(value);
  1917. }
  1918. else{
  1919. let value = $(this).val();
  1920. if(value >= 0 && value <= 1){
  1921. $(this).prev().val(value);
  1922. }
  1923. else{
  1924. if(value < 0){
  1925. $(this).val(0);
  1926. }
  1927. else{
  1928. $(this).val(1);
  1929. }
  1930. }
  1931. }
  1932. });
  1933.  
  1934.  
  1935. $('body').on('click','a[data-needed="direct"]', function(e){
  1936. e.preventDefault();
  1937. triggerLinkElement(this);
  1938. });
  1939.  
  1940. $('body').on('click','.IG_SN_DIG_BODY .newTab', function(){
  1941. openNewTab($(this).parent().children('a').attr('data-href'));
  1942. });
  1943.  
  1944. $('body').on('click','.IG_SN_DIG_BODY .videoThumbnail', function(){
  1945. saveFiles($(this).parent().children('a').find('img').first().attr('src'), $(this).parent().children('a').attr('data-username'), 'thumbnail', new Date().getTime(), 'jpg', $('#article-id').text());
  1946. });
  1947.  
  1948. // Running if user left-click download icon in stories
  1949. $('body').on('click','.IG_DWSTORY',function(){
  1950. onStory(true);
  1951. });
  1952. $('body').on('click','.IG_DWNEWTAB',function(e){
  1953. e.preventDefault();
  1954. onStory(true, true, true);
  1955. });
  1956.  
  1957. // Running if user left-click download icon in stories
  1958. $('body').on('click','.IG_DWSTORY_THUMBNAIL',function(){
  1959. onStoryThumbnail(true);
  1960. });
  1961.  
  1962. // Running if user left-click download icon in profile
  1963. $('body').on('click','.IG_DWPROFILE',function(e){
  1964. e.stopPropagation();
  1965. onProfileAvatar(true);
  1966. });
  1967.  
  1968. // Running if user left-click download icon in highlight stories
  1969. $('body').on('click','.IG_DWHISTORY',function(){
  1970. onHighlightsStory(true);
  1971. });
  1972. $('body').on('click','.IG_DWHINEWTAB',function(e){
  1973. e.preventDefault();
  1974. onHighlightsStory(true, true);
  1975. });
  1976.  
  1977. // Running if user left-click download icon in highlight stories
  1978. $('body').on('click','.IG_DWHISTORY_THUMBNAIL',function(){
  1979. onHighlightsStoryThumbnail(true);
  1980. });
  1981.  
  1982. // Running if user left-click download icon in reels
  1983. $('body').on('click','.IG_REELS',function(){
  1984. onReels(true,true);
  1985. });
  1986.  
  1987. // Running if user left-click newtab icon in reels
  1988. $('body').on('click','.IG_REELSNEWTAB',function(){
  1989. onReels(true,true,true);
  1990. });
  1991.  
  1992. // Running if user left-click download icon in reels
  1993. $('body').on('click','.IG_REELS_THUMBNAIL',function(){
  1994. onReels(true,false);
  1995. });
  1996.  
  1997. // Running if user right-click profile picture in stories area
  1998. $('body').on('contextmenu','button[role="menuitem"]',function(){
  1999. if(location.href === 'https://www.instagram.com/' && USER_SETTING.REDIRECT_RIGHT_CLICK_USER_STORY_PICTURE){
  2000. if($(this).find('canvas._aarh').length > 0){
  2001. location.href = 'https://www.instagram.com/'+$(this).children('div').last().text();
  2002. }
  2003. }
  2004. });
  2005.  
  2006. $('body').on('change', '.IG_SN_DIG_TITLE .checkbox', function(){
  2007. var isChecked = $(this).find('input').prop('checked');
  2008. $('.IG_SN_DIG_BODY .inner_box').each(function(){
  2009. $(this).prop('checked', isChecked);
  2010. });
  2011. });
  2012.  
  2013. $('body').on('change', '.IG_SN_DIG_BODY .inner_box', function(){
  2014. var checked = $('.IG_SN_DIG_BODY .inner_box:checked').length;
  2015. var total = $('.IG_SN_DIG_BODY .inner_box').length;
  2016.  
  2017.  
  2018. $('.IG_SN_DIG_TITLE .checkbox').find('input').prop('checked', checked == total);
  2019. });
  2020.  
  2021. $('body').on('click', '.IG_SN_DIG_TITLE #batch_download_selected', function(){
  2022. let index = 0;
  2023. $('.IG_SN_DIG_BODY a[data-needed="direct"]').each(function(){
  2024. if($(this).prev().children('input').prop('checked')){
  2025. $(this).click();
  2026. index++;
  2027. }
  2028. });
  2029.  
  2030. if(index == 0){
  2031. alert(_i18n('NO_CHECK_RESOURCE'));
  2032. }
  2033. });
  2034.  
  2035. $('body').on('click', '.IG_SN_DIG_TITLE #batch_download_direct', function(){
  2036. $('.IG_SN_DIG_BODY a[data-needed="direct"]').each(function(){
  2037. $(this).click();
  2038. });
  2039. });
  2040. });
  2041. })(jQuery);