IG小助手

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

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

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