Arca base64 autodecoder

Arca.live Base64 auto decoder

当前为 2023-12-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Arca base64 autodecoder
  3. // @name:ko 아카라이브 Base64 자동 디코더
  4. // @version 1.204
  5. // @author Laria
  6. // @match https://arca.live/b/*/*
  7. // @description Arca.live Base64 auto decoder
  8. // @description:ko 아카라이브 Base64 자동 복호화 스크립트
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=arca.live
  10. // @license MIT
  11. // @encoding utf-8
  12. // @run-at document-end
  13. // @supportURL https://greasyfork.org/ko/scripts/482577-arca-base64-autodecoder
  14. // @namespace https://greasyfork.org/users/1235854
  15. // @grant GM.getValue
  16. // @grant GM.setValue
  17. // @grant GM.deleteValue
  18. // @grant GM.registerMenuCommand
  19. // @grant GM.unregisterMenuCommand
  20. // ==/UserScript==
  21.  
  22. /*
  23. * == Change log ==
  24. * 1.0 - Release
  25. * 1.1 - Invalid character update (replace -> replaceAll)
  26. * 1.11 - Improved show multiple links
  27. * 1.12 - Show Single links Bugfix
  28. * 1.13 - Bugfix 1.12
  29. * 1.14 - Base64 add padding func
  30. * 1.15 - Add annotation, display improvements
  31. * 1.16 - Display improved - CSS applied
  32. * 1.17 - var safe, max_iter defined (~7, def:3)
  33. * 1.18 - auto update check, log system
  34. * 1.20 - add menu(base64 depth, user-drag auto decoding, hide encoded link, update notify)
  35. * 1.201 - base64 depth extends - 11, temporary disable - drag auto decoding
  36. * 1.202 - improve encoded link click callback, feature block in edit mode, enable drag auto decoding
  37. * 1.203 - add menu(restore defaults)
  38. * 1.204 - set update check interval -> 1day(86400), seperate localparameter
  39. */
  40.  
  41. //base64 encoded(http:/*, https:/*) string prefix
  42. const regexEncodedPrefix = [
  43. /(aHR0cDovL|aHR0cHM6Ly)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 1 time
  44. /(YUhSMGNEb3ZM|YUhSMGNITTZMe)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 2 time
  45. /(WVVoU01HTkViM1pN|WVVoU01HTklUVFpNZ)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 3 time
  46. /(V1ZWb1UwMUhUa1ZpTTFwT|V1ZWb1UwMUhUa2xVVkZwTl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 4 time
  47. /(VjFaV2IxVXdNVWhVYTFacFRURndU|VjFaV2IxVXdNVWhVYTJ4VlZrWndUb)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 5 time
  48. /(VmpGYVYySXhWWGROVldoVllURmFjRlJVUm5kV|VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVW)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 6 time
  49. /(Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVUm1GalJsSlZVbTVrV|Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVU2pSV2JGcHlWMjVrVl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 7 time
  50. /(Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVbTFHYWxKc1NsWlZiVFZyV|Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVMnBTVjJKR2NIbFdNalZyVm)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 8 time
  51. /(Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZiVEZIWVd4S2MxTnNXbFppVkZaeV|Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZNbkJUVmpKS1IyTkliRmROYWxaeVZt)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 9 time
  52. /(Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFppVkVaSVdWZDRTMk14VG5OWGJGcHBWa1phZ|Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZad)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 10 time
  53. /(Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcHBWa1ZhU1ZkV1pEUlRNazE0Vkc1T1dHSkdjSEJXYTFwaF|Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcE9ZbXRLVlZadGNFdFRNVWw1Vkd0c2FWSnRVazlaVjNoaFpWWmFk)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 11 time
  54. ];
  55.  
  56. //regex prefix - drag
  57. const regInvalid = /[^\w\+\/=]/;
  58.  
  59. //update check interval (sec, def:1 day(86400))
  60. const updateInterval = 86400;
  61.  
  62. //update chk, fail->false
  63. let updateAvailble = true;
  64.  
  65. //auto drag decoding enable status
  66. let draggableActivated = false;
  67.  
  68. //encoded link list, [uuid]: [encoded link]
  69. let encodedList = {};
  70.  
  71. //total decode count
  72. let hindex = 0;
  73.  
  74. //drag function comparison
  75. let lastSelected = document;
  76. let lastSelectedTime = Date.now();
  77.  
  78. //logging prefix, param
  79. const sc_name = '['+GM.info.script.name+']';
  80. const sc_name_upd = '['+GM.info.script.name+'-UPD]';
  81. const sc_name_par = '['+GM.info.script.name+'-PAR]';
  82.  
  83. //script local parameter
  84. let localParameter = {
  85. 'lastupdate': {
  86. 'param_name': 'lastupdate',
  87. 'value': 0,
  88. 'def_value': 0,
  89. },
  90. 'basedepth': {
  91. 'param_name': 'basedepth',
  92. 'value': 3,
  93. 'def_value': 3,
  94. },
  95. 'enclinkhide': {
  96. 'param_name': 'enclinkhide',
  97. 'value': false,
  98. 'def_value': false,
  99. },
  100. 'draggable': {
  101. 'param_name': 'draggable',
  102. 'value': false,
  103. 'def_value': false,
  104. },
  105. 'updatechk': {
  106. 'param_name': 'chkupd',
  107. 'value': true,
  108. 'def_value': true,
  109. },
  110. };
  111.  
  112. //script menu structure
  113. let menuStructure = {
  114. 'basedepth': {
  115. 'param_name': localParameter.basedepth,
  116. 'name': 'base64 깊이 조절하기 - 현재 값 : 알수없음',
  117. 'desc': '자동 base64 디코딩 깊이를 조절할 수 있습니다.',
  118. 'id': -1,
  119. 'func': menuFunctionBasedepth,
  120. 'visible': true,
  121. },
  122. 'enclinkhide': {
  123. 'param_name': localParameter.enclinkhide,
  124. 'name': '인코딩된 링크 [보이기/숨기기]',
  125. 'desc': '자동 base64 디코딩 전 인코딩된 링크 표시 여부를 설정할 수 있습니다.',
  126. 'id': -1,
  127. 'func': menuFunctionEnchide,
  128. 'visible': true,
  129. },
  130. 'draggable': {
  131. 'param_name': localParameter.draggable,
  132. 'name': '드래그 시 자동 디코딩 [켜기/끄기]',
  133. 'desc': '드래그 시 자동으로 드래그한 부분을 base64로 디코딩할지 설정할 수 있습니다.',
  134. 'id': -1,
  135. 'func': menuFunctionDraggable,
  136. 'visible': true,
  137. },
  138. 'updatechk': {
  139. 'param_name': localParameter.updatechk,
  140. 'name': '업데이트 알림 [켜기/끄기]',
  141. 'desc': '새 버전이 나올 시 업데이트 확인 알림을 띄울지 여부를 설정할 수 있습니다.',
  142. 'id': -1,
  143. 'func': menuFunctionUpdateCheck,
  144. 'visible': true,
  145. },
  146. 'resetdefaults': {
  147. 'param_name': null,
  148. 'name': '스크립트 기본값 초기화',
  149. 'desc': '스크립트의 사용자 설정을 초기화하고 설치 상태로 되돌립니다.',
  150. 'id': -1,
  151. 'func': menuFunctionRstDefaults,
  152. 'visible': true,
  153. },
  154. };
  155.  
  156. //auto add padding - add '=' padding in base64 encoded string
  157. function base64AddPadding(str) {
  158. return str + Array((4 - str.length % 4) % 4 + 1).join('=');
  159. }
  160.  
  161. //base64 decode
  162. const Base64 = {
  163. _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  164. decode : function (input) {
  165. let output = "";
  166. let chr1, chr2, chr3;
  167. let enc1, enc2, enc3, enc4;
  168. let i = 0;
  169.  
  170. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  171.  
  172. while (i < input.length) {
  173. enc1 = this._keyStr.indexOf(input.charAt(i++));
  174. enc2 = this._keyStr.indexOf(input.charAt(i++));
  175. enc3 = this._keyStr.indexOf(input.charAt(i++));
  176. enc4 = this._keyStr.indexOf(input.charAt(i++));
  177.  
  178. chr1 = (enc1 << 2) | (enc2 >> 4);
  179. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  180. chr3 = ((enc3 & 3) << 6) | enc4;
  181.  
  182. //last bits
  183. output = output + String.fromCharCode(chr1);
  184. if (enc3 != 64) { //=
  185. output = output + String.fromCharCode(chr2);
  186. }
  187. if (enc4 != 64) { //==
  188. output = output + String.fromCharCode(chr3);
  189. }
  190. }
  191.  
  192. output = Base64._utf8_decode(output);
  193. return output;
  194. },
  195. // private method for UTF-8 decoding
  196. _utf8_decode : function (utftext) {
  197. let string = "";
  198. let i = 0;
  199. let c = 0;
  200. let c1 = 0;
  201. let c2 = 0;
  202. let c3 = 0;
  203.  
  204. while ( i < utftext.length ) {
  205. c = utftext.charCodeAt(i);
  206. if (c < 128) {
  207. string += String.fromCharCode(c);
  208. i++;
  209. }
  210. else if((c > 191) && (c < 224)) {
  211. c2 = utftext.charCodeAt(i+1);
  212. string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  213. i += 2;
  214. }
  215. else {
  216. c2 = utftext.charCodeAt(i+1);
  217. c3 = utftext.charCodeAt(i+2);
  218. string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  219. i += 3;
  220. }
  221. }
  222. return string;
  223. }
  224. };
  225.  
  226. //encoded link click callback
  227. function showEncodedLink(event) {
  228. const self = event.currentTarget;
  229. //check already clicked
  230. if (encodedList.hasOwnProperty(self.id)) {
  231. console.log(sc_name,'show encoded link -',encodedList[self.id]);
  232. self.innerHTML = encodedList[self.id];
  233. self.style.color = 'rgb(71 88 188)';
  234. self.title = '디코딩 전 인코딩된 링크입니다.';
  235. //delete encodedList[self.id];
  236. }
  237. return;
  238. }
  239.  
  240. //link area
  241. function createEncodedLink(src) {
  242. return '<span style="font-size: 87.5%;color: rgb(71 188 115);">[ ' + src.toString() + ' ]</span>';
  243. }
  244.  
  245. //encoded link element
  246. function createMaskEncodedLink(src) {
  247. const uuid = 'abad_'+self.crypto.randomUUID();
  248. encodedList[uuid] = src;
  249. return '<span id="' + uuid.toString() + '" title="클릭 시 디코딩 전 인코딩된 링크를 표시합니다.">' + '클릭 시 인코딩된 코드 보기' + '</span>';
  250. }
  251.  
  252. //link creation
  253. function createLink(src, index, url, depth, hidelink = false) {
  254. //n번째 링크 (base64 깊이: 0) [ ABCDEF= / 클릭시 원본~ ]
  255. return '<a href="'+url+'" title="'+url+' (새 창으로 열기)" target="_blank" rel="external nofollow noopener noreferrer">'+index.toString()+'번째 링크 (base64 깊이: '+depth.toString()+')</a> '+(hidelink?createEncodedLink(createMaskEncodedLink(src)):createEncodedLink(src))+'';
  256. }
  257.  
  258. //decode & generate
  259. function replacerGen(numIter) {
  260. return function(source) {
  261. try {
  262. let rstring = ""; //return msg
  263. console.log('\n'+sc_name,'No.',(hindex+1),'encoded link:\n', source.toString()); //source
  264.  
  265. //decode
  266. let converted = Base64.decode(base64AddPadding(source));
  267. //attempt to decode nested base64 encoded string
  268. for(let i=0; i<numIter; i++) {
  269. converted = Base64.decode(base64AddPadding(converted));
  270. }
  271. hindex++;
  272.  
  273. //remove invalid string - �
  274. converted = decodeURI(encodeURI(converted).replaceAll('%00', ''));
  275. console.log(sc_name,'No.',hindex,'decode completed (depth:',numIter+1,'):\n',converted.toString()); //converted
  276.  
  277. //split by new line
  278. converted = converted.split(/\r?\n/);
  279. //single component
  280. if (converted.length == 2 && converted[converted.length-1] == '') {
  281. rstring += createLink(source, hindex, converted[0], numIter+1, !localParameter.enclinkhide.value);
  282. //multiple component
  283. } else if (converted.length > 1) {
  284. rstring += createEncodedLink(localParameter.enclinkhide.value?source.toString():createMaskEncodedLink(source.toString()));
  285.  
  286. let nindex = 1;
  287. const hindexPrev = hindex;
  288. converted.forEach(function(i) {
  289. if (i != '') {
  290. rstring += '<br>' + createLink('<span style="color: rgb(71 188 115);" title="자동으로 분할된 '+nindex.toString()+'번째 링크입니다.">링크 자동 분할 : '+nindex.toString()+'번째</span>', hindex, i, numIter+1);
  291. hindex++;
  292. nindex++;
  293. }
  294. });
  295. //apply last components
  296. hindex--;
  297. nindex--;
  298.  
  299. console.log(sc_name,'No.',hindexPrev,'- splitted total :', nindex);
  300. rstring = '<span style="color: rgb(232 62 140);"><b><i>분할된 링크 총 '+nindex.toString()+'개</i></b></span> ' + rstring;
  301. } else rstring += createLink(source, hindex, converted, numIter+1, !localParameter.enclinkhide.value);
  302. return rstring;
  303. } catch(e) {
  304. console.warn('\n'+sc_name,'error occured during decoding:', e);
  305. console.warn(sc_name,'base64 decode fail:', source);
  306. }
  307. return '<span style="color: rgb(255 0 0);">[ base64 변환 실패: '+source.toString()+' ]</span>';
  308. };
  309. }
  310.  
  311. //user drag event
  312. function selClicked(event) {
  313. const sel = document.getSelection().toString();
  314. if (!sel.match(regInvalid) && sel.length >= 10 && lastSelectedTime + 200 < Date.now()) {
  315. try {
  316. console.log(sc_name,'live match -',sel.toString());
  317. let converted = decodeURI(encodeURI(Base64.decode(base64AddPadding(sel))).replaceAll('%00', ''));
  318. console.log(sc_name,'converted -',converted.toString());
  319. this.innerHTML = this.innerHTML.replace(sel, converted)+' ';
  320. } catch (e) {
  321. return;
  322. } finally {
  323. this.removeEventListener('click', selClicked);
  324. }
  325. }
  326. }
  327.  
  328. //user drag activate
  329. function activateDragDecoding() {
  330. if(draggableActivated) {
  331. console.log(sc_name,'USR-Drag already enabled.');
  332. return;
  333. }
  334. draggableActivated = true;
  335. console.log(sc_name,'USR-Drag enabled.');
  336. document.addEventListener('selectionchange', function() {
  337. let sel = document.getSelection().anchorNode;
  338. if(sel) {
  339. sel = sel.parentElement;
  340. if(sel != lastSelected) {
  341. lastSelected.removeEventListener('click', selClicked);
  342. sel.addEventListener('click', selClicked);
  343. lastSelected = sel;
  344. lastSelectedTime = Date.now();
  345. }
  346. }
  347. });
  348. }
  349.  
  350. //update check
  351. function checkForUpdate(){
  352. if (!updateAvailble || !localParameter.updatechk.value) {
  353. console.log(sc_name_upd,'updchk skipped.');
  354. return;
  355. }
  356. const currentTime = Math.floor(new Date().getTime() / 1000);
  357. if (currentTime - localParameter.lastupdate.value < updateInterval) {
  358. console.log(sc_name_upd,'updchk already done in 1 day.. skip updchk');
  359. return;
  360. }
  361. GM.setValue(localParameter.lastupdate.param_name, currentTime);
  362.  
  363. console.log(sc_name_upd,'checking for update...');
  364. const updateLink = 'https://update.greasyfork.org/scripts/482577/Arca%20base64%20autodecoder.meta.js';
  365. fetch(updateLink)
  366. .then(response => response.text())
  367. .then(data => {
  368. //extract version from greaskyfork script
  369. const match = data.match(/@version\s+(\d+\.\d+)/);
  370. if (match) {
  371. const tar_version = parseFloat(match[1]);
  372. const cur_version = parseFloat(GM.info.script.version);
  373. //new version detected
  374. if (tar_version > cur_version) {
  375. console.log(sc_name_upd,'new version available. ('+cur_version+' -> '+tar_version+')');
  376. //y/n dialog
  377. if (window.confirm(sc_name+'\n새로운 버전이 감지되었습니다. 업데이트를 권장합니다.\n( 기존버전 : '+cur_version+', 새로운 버전 : '+tar_version+' )\n(변경사항은 아카라이브 게시글을 참고해주세요.)\n\n취소를 누르면 앞으로 업데이트 알림을 띄우지 않습니다.')) {
  378. //get extension env
  379. if(!GM.info.scriptWillUpdate) {
  380. console.log(sc_name_upd,'extension not allowed auto update..');
  381. if (window.confirm(sc_name+'\n주의! 스크립트 내용 변경 등으로 인해 확장프로그램 내 자동 업데이트가 꺼져있는 것 같습니다.\n업데이트 시 기존 스크립트에 덮어쓰게 되어 기존 내용이 손실될 수 있습니다.\n이 점 확인 후 업데이트 바랍니다.\n\n(계속하려면 확인, 취소하려면 취소를 눌러주세요.)')) {
  382. console.log(sc_name_upd,'opening source url..');
  383. window.location.replace(updateLink);
  384. window.alert(sc_name+'\n업데이트 후 새로고침해야 적용됩니다.');
  385. } else {
  386. console.log(sc_name_upd,"user canceled.");
  387. }
  388. } else {
  389. console.log(sc_name_upd,'opening source url..');
  390. window.location.replace(updateLink);
  391. window.alert(sc_name+'\n업데이트 후 새로고침해야 적용됩니다.');
  392. }
  393. } else {
  394. console.log(sc_name_par,'updatechk change',true.toString(),'to',false.toString());
  395. try {
  396. GM.setValue(localParameter.updatechk.param_name, false);
  397. localParameter.updatechk.value = false;
  398. console.log(sc_name_par,"updatechk change successful");
  399. menuStructureUpdate();
  400. window.alert(sc_name+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
  401. } catch(e) {
  402. localParameter.updatechk.value = true;
  403. console.error(sc_name_par,"updatechk change fail -", e);
  404. window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
  405. }
  406. }
  407. } else {
  408. console.log(sc_name_upd,'latest version', cur_version, 'detected. (eth:',tar_version,')');
  409. }
  410. } else {
  411. console.error(sc_name_upd,'unable to extract version..');
  412. }
  413. })
  414. .catch(error => {
  415. updateAvailble = false;
  416. console.error(sc_name_upd,'link unreachable.. -', error);
  417. });
  418. updateAvailble = false;
  419. }
  420.  
  421. //menu update
  422. function menuStructureUpdate(fistRun = false) {
  423. //pre process
  424. localParameter.basedepth.value = localParameter.basedepth.value > regexEncodedPrefix.length ? regexEncodedPrefix.length : localParameter.basedepth.value;
  425.  
  426. //update menu name
  427. menuStructure.basedepth.name = 'base64 깊이 조절하기 - 현재 값 : '+localParameter.basedepth.value+'회';
  428. menuStructure.enclinkhide.name = '인코딩된 링크 '+(localParameter.enclinkhide.value?'숨기기':'보이기');
  429. menuStructure.draggable.name = '드래그 시 자동 디코딩 '+(localParameter.draggable.value?'끄기':'켜기');
  430. menuStructure.updatechk.name = '업데이트 알림 '+(localParameter.updatechk.value?'끄기':'켜기');
  431.  
  432. //remove exist menu cmd
  433. if (!fistRun) {
  434. Object.keys(menuStructure).forEach(function(i){
  435. try {
  436. GM.unregisterMenuCommand(menuStructure[i].id);
  437. } catch(_) {}
  438. });
  439. }
  440. //monkey menu cmd register
  441. try {
  442. Object.keys(menuStructure).forEach(function(i){
  443. if (menuStructure[i].visible) {
  444. menuStructure[i].id = GM.registerMenuCommand(menuStructure[i].name, menuStructure[i].func, {title:menuStructure[i].desc});
  445. } else {
  446. //if invisible -> use default parameter
  447. if (localParameter.hasOwnProperty(i)) {
  448. localParameter[i].value = localParameter[i].def_value;
  449. }
  450. }
  451. });
  452. console.log(sc_name_par,'ext opt pannel',(fistRun?'registered':'reloaded'));
  453. } catch(e) {
  454. console.error(sc_name_par,'err - ext opt pannel',(fistRun?'register':'reload'),'- ', e);
  455. Object.keys(menuStructure).forEach(function(i){
  456. try {
  457. GM.unregisterMenuCommand(menuStructure[i].id);
  458. } catch(_) {}
  459. });
  460. }
  461. }
  462.  
  463. function menuFunctionBasedepth() {
  464. menuStructureUpdate();
  465. const previousValue = localParameter.basedepth.value;
  466. const str_common_1 = ' ( 지정 가능한 범위: 1~'+regexEncodedPrefix.length.toString()+' )';
  467. while(true) {
  468. const input = window.prompt(sc_name+'\nBase64 자동 디코딩 중첩 횟수를 얼마로 지정할까요?\n(인코딩을 인코딩한 것을 여러번 반복한걸 자동으로 풀어냅니다.)\n현재 값: '+previousValue.toString()+'회,'+(previousValue == 3 ? '' : ' 기본값: 3회,')+str_common_1+'\n\n(값을 너무 크게 지정하면 컴퓨터 성능에 영향을 줄 수 있습니다.)', previousValue);
  469. if (input == null) {
  470. console.log(sc_name,'basedepth change canceled.');
  471. break;
  472. }
  473. if(!isNaN(input)){
  474. const targetValue = parseInt(input);
  475. if(targetValue == previousValue) {
  476. window.alert(sc_name+'\n동일한 값을 입력했습니다, 현재 값: '+previousValue+'회');
  477. } else if(targetValue >= 1 && targetValue <= regexEncodedPrefix.length) {
  478. console.log(sc_name_par,'basedepth change',previousValue.toString(),'to',targetValue.toString());
  479. localParameter.basedepth.value = targetValue;
  480. try {
  481. GM.setValue('basedepth', targetValue);
  482. console.log(sc_name_par,"basedepth change successful");
  483. window.alert(sc_name+'\n값이 '+previousValue.toString()+'에서 '+targetValue.toString()+'으로 변경이 완료되었습니다.\n\n(사이트를 새로고침해야 반영됩니다.)');
  484. } catch(e) {
  485. localParameter.basedepth.value = previousValue;
  486. console.error(sc_name_par,"basedepth change fail -", e);
  487. window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
  488. } finally {
  489. menuStructureUpdate();
  490. }
  491. break;
  492. } else {
  493. window.alert(sc_name+'\n'+targetValue+'(으)로 설정할 수 없습니다.\n범위를 초과하였습니다..'+str_common_1);
  494. }
  495. } else {
  496. window.alert(sc_name+'\n'+input+'은(는)숫자가 아닙니다.\n숫자만 입력해주세요..'+str_common_1);
  497. }
  498. }
  499. }
  500.  
  501. function menuFunctionEnchide() {
  502. menuStructureUpdate();
  503. const currentState = localParameter.enclinkhide.value;
  504. if(window.confirm(sc_name+'\n디코딩 시 인코딩된 링크를 '+(currentState?'숨기시':'표시하')+'겠습니까?\n\n(앞으로 디코딩 전 인코딩된 링크를\n"'+(currentState?'클릭 시 기존링크 보기':'aHR0cHM6Ly9hcmNhLmx..')+'"와 같은 형태로 보여줍니다.)')) {
  505. const targetState = !currentState;
  506. console.log(sc_name_par,'enchide change',currentState.toString(),'to',targetState.toString());
  507. localParameter.enclinkhide.value = targetState;
  508. try {
  509. GM.setValue('enclinkhide', targetState);
  510. console.log(sc_name_par,"updatechk change successful");
  511. if(targetState) {
  512. window.alert(sc_name+'\n앞으로 인코딩된 링크를 표시합니다.\n\n(새로고침해야 적용됩니다.)');
  513. } else {
  514. window.alert(sc_name+'\n앞으로 인코딩된 링크를 숨깁니다.');
  515. }
  516. } catch(e) {
  517. localParameter.enclinkhide.value = currentState;
  518. console.error(sc_name_par,"enchide change fail -", e);
  519. window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
  520. } finally {
  521. menuStructureUpdate();
  522. }
  523. } else {
  524. console.log(sc_name,'enchide change canceled.');
  525. }
  526. }
  527.  
  528. function menuFunctionDraggable() {
  529. menuStructureUpdate();
  530. const currentState = localParameter.draggable.value;
  531. if(window.confirm(sc_name+'\n드래그 시 자동 디코딩을 '+(currentState?'비':'')+'활성화 하시겠습니까?\n\n(앞으로 인코딩된 부분을 드래그'+(currentState?'해도 자동으로 디코딩되지 않습':' 시 Base64로 인코딩된것으로\n판단 되면 자동으로 디코딩을 시도합')+'니다.)\n\n(이 기능은 작동이 불안정할 수 있습니다.)')) {
  532. const targetState = !currentState;
  533. console.log(sc_name_par,'draggable change',currentState.toString(),'to',targetState.toString());
  534. localParameter.draggable.value = targetState;
  535. try {
  536. GM.setValue('draggable', targetState);
  537. console.log(sc_name_par,"draggable change successful");
  538. if(targetState) {
  539. try {
  540. activateDragDecoding();
  541. window.alert(sc_name+'\n앞으로 드래그 시 자동 디코딩을 진행합니다.');
  542. } catch(e) {
  543. console.error(sc_name,"draggable activate fail -", e);
  544. window.alert(sc_name+'\n드래그 시 자동 디코딩 활성화 중 문제가 발생했습니다.\n새로고침이 필요합니다..');
  545. }
  546. } else {
  547. window.alert(sc_name+'\n앞으로 드래그 해도 반응하지 않습니다.\n\n(새로고침해야 적용됩니다.)');
  548. }
  549. } catch(e) {
  550. localParameter.draggable.value = currentState;
  551. console.error(sc_name_par,"draggable change fail -", e);
  552. window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
  553. } finally {
  554. menuStructureUpdate();
  555. }
  556. } else {
  557. console.log(sc_name,'draggable change canceled.');
  558. }
  559. }
  560.  
  561. function menuFunctionUpdateCheck() {
  562. menuStructureUpdate();
  563. const currentState = localParameter.updatechk.value;
  564. if(window.confirm(sc_name+'\n업데이트 알림을 '+(currentState?'끄':'켜')+'시겠습니까?\n\n(앞으로 업데이트가 있'+(currentState?'어도 알려주지 않습':'으면 자동으로 알려줍')+'니다.)')) {
  565. const targetState = !currentState;
  566. console.log(sc_name_par,'updatechk change',currentState.toString(),'to',targetState.toString());
  567. localParameter.updatechk.value = targetState;
  568. try {
  569. GM.setValue(localParameter.updatechk.param_name, targetState);
  570. console.log(sc_name_par,"updatechk change successful");
  571. if(targetState) {
  572. window.alert(sc_name+'\n앞으로 업데이트가 존재하면 알림을 띄웁니다.');
  573. } else {
  574. window.alert(sc_name+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
  575. }
  576. } catch(e) {
  577. localParameter.updatechk.value = currentState;
  578. console.error(sc_name_par,"updatechk change fail -", e);
  579. window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
  580. } finally {
  581. menuStructureUpdate();
  582. }
  583. } else {
  584. console.log(sc_name,'updatechk change canceled.');
  585. }
  586. }
  587.  
  588. function menuFunctionRstDefaults() {
  589. menuStructureUpdate();
  590. if(window.confirm(sc_name+'\n정말 스크립트 설정을 기본값으로 초기화하시겠습니까?\n\n(초기화 완료 후 자동으로 새로고침됩니다.)')) {
  591. try {
  592. console.log(sc_name_par, 'remove all settings..');
  593. for (const i of Object.keys(localParameter)) {
  594. GM.deleteValue(localParameter[i].param_name);
  595. }
  596. console.log(sc_name_par, 'all parameter removed.');
  597. window.alert(sc_name+'\n설정값이 모두 제거되었습니다.\n\n(확인 후 현재 창이 자동으로 새로고침됩니다.)');
  598. window.location.reload(true);
  599. } catch(e) {
  600. console.error(sc_name_par,'err - get sc parameter - ', e);
  601. window.alert(sc_name+'\n경고! 파라미터 초기화 도중 문제가 발생했습니다. 로그를 참고해주세요..');
  602. }
  603. } else {
  604. console.log(sc_name,'settings restore canceled.');
  605. }
  606. }
  607.  
  608. //main
  609. (async () => {
  610. 'use strict';
  611.  
  612. //chk browser env
  613. if (((navigator.language || navigator.userLanguage) != 'ko-KR')) console.warn('Warning! this script support only korean language..');
  614.  
  615. let url_tail = window.location.href.match(/([/][a-z0-9_-]*[\/]?)$/g);
  616. if(url_tail != null) {
  617. if(url_tail[0] == '/edit') {
  618. console.log(sc_name,'edit mode detected, function disabled.');
  619. try {GM.registerMenuCommand("Edit 모드에서는 작동하지 않음", ()=>{window.alert(sc_name+'\n작성 또는 수정모드에서는 작동하지 않습니다..');});} catch(_) {}
  620. return;
  621. }
  622. }
  623.  
  624. console.log(sc_name,'V',GM.info.script.version,'enabled');
  625.  
  626. //load parameter
  627. try {
  628. for (const i of Object.keys(localParameter)) {
  629. localParameter[i].value = await GM.getValue(localParameter[i].param_name, localParameter[i].def_value);
  630. }
  631. console.log(sc_name_par, 'sc parameter load successful.');
  632. } catch(e) {
  633. console.error(sc_name_par,'err - get sc parameter - ', e);
  634. }
  635.  
  636. //apply parameter and register monkey menu command
  637. menuStructureUpdate(true);
  638.  
  639. //chk update
  640. await checkForUpdate();
  641.  
  642. //drag auto decoding
  643. if (localParameter.draggable.value) {
  644. activateDragDecoding();
  645. }
  646.  
  647. console.log(sc_name,'ready');
  648. //main procedure
  649.  
  650. //article
  651. let article = document.getElementsByClassName("article-content")[0];
  652. if (article != undefined) {
  653. for(let i=0; i<localParameter.basedepth.value; i++) {
  654. article.innerHTML = article.innerHTML.replaceAll(regexEncodedPrefix[i], replacerGen(i));
  655. }
  656. } else console.warn(sc_name,'no article detected.');
  657.  
  658. //comment
  659. let comments = document.getElementsByClassName("list-area");
  660. if (article != undefined) {
  661. if (comments.length != 0) {
  662. for(let i=0; i<localParameter.basedepth.value; i++) {
  663. comments[0].innerHTML = comments[0].innerHTML.replaceAll(regexEncodedPrefix[i], replacerGen(i));
  664. }
  665. }
  666. } else console.warn(sc_name,'no comments detected.');
  667. console.log(sc_name,'total',hindex,'link decode task completed.');
  668.  
  669. //add eventlistner - click, show original encoded link
  670. if (!localParameter.enclinkhide.value) {
  671. Object.keys(encodedList).forEach(function(i) {
  672. document.getElementById(i).addEventListener('click', showEncodedLink, { once : true } );
  673. });
  674. }
  675. })();