Arca base64 autodecoder

auto decode Base64 encoded link in Arca.live

当前为 2024-01-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Arca base64 autodecoder
  3. // @name:ko 아카라이브 Base64 자동 디코더
  4. // @version 1.212
  5. // @author Laria
  6. // @match https://arca.live/b/*/*
  7. // @description auto decode Base64 encoded link in Arca.live
  8. // @description:ko 아카라이브 내 Base64로 인코딩된 링크를 자동으로 복호화합니다.
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=arca.live
  10. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  11. // @license MIT
  12. // @encoding utf-8
  13. // @run-at document-end
  14. // @supportURL https://greasyfork.org/ko/scripts/482577-arca-base64-autodecoder
  15. // @namespace https://greasyfork.org/users/1235854
  16. // @grant GM.getValue
  17. // @grant GM.setValue
  18. // @grant GM.deleteValue
  19. // @grant GM.registerMenuCommand
  20. // @grant GM.unregisterMenuCommand
  21. // @grant GM.setClipboard
  22. // ==/UserScript==
  23.  
  24. /*
  25. * == Change log ==
  26. * 1.0 - Release
  27. * 1.1 - Invalid character update (replace -> replaceAll)
  28. * 1.11 - Improved show multiple links
  29. * 1.12 - Show Single links Bugfix
  30. * 1.13 - Bugfix 1.12
  31. * 1.14 - Base64 add padding func
  32. * 1.15 - Add annotation, display improvements
  33. * 1.16 - Display improved - CSS applied
  34. * 1.17 - var safe, max_iter defined (~7, def:3)
  35. * 1.18 - auto update check, log system
  36. * 1.20 - add menu(base64 depth, user-drag auto decoding, hide encoded link, update notify)
  37. * 1.201 - base64 depth extends - 11, temporary disable - drag auto decoding
  38. * 1.202 - improve encoded link click callback, feature block in edit mode, enable drag auto decoding
  39. * 1.203 - add menu(restore defaults)
  40. * 1.204 - set update check interval -> 1day(86400), seperate localparameter
  41. * 1.205 - url chk add(write), code stabilization
  42. * 1.206 - add menu(expand menu), newline, encoded link copy function, show url hostname
  43. * 1.207 - show total decoded count on article top, update link fix/improve redirection, update chk interval modify(86400 -> 21600)
  44. * 1.21 - window alert/confirm -> swal2 gui
  45. * 1.211 - version fix
  46. * 1.212 - remove unavailble function
  47. */
  48.  
  49. /*
  50. * == TODO ==
  51. * auto decoding newline/space
  52. * detect channel => specific decoding
  53. * show warning message(redirection)
  54. */
  55.  
  56. //base64 encoded(http:/*, https:/*) string prefix
  57. const regexEncodedPrefixDef = [
  58. /(aHR0cDovL|aHR0cHM6Ly)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 1 time
  59. /(YUhSMGNEb3ZM|YUhSMGNITTZMe)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 2 time
  60. /(WVVoU01HTkViM1pN|WVVoU01HTklUVFpNZ)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 3 time
  61. /(V1ZWb1UwMUhUa1ZpTTFwT|V1ZWb1UwMUhUa2xVVkZwTl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 4 time
  62. /(VjFaV2IxVXdNVWhVYTFacFRURndU|VjFaV2IxVXdNVWhVYTJ4VlZrWndUb)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 5 time
  63. /(VmpGYVYySXhWWGROVldoVllURmFjRlJVUm5kV|VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVW)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 6 time
  64. /(Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVUm1GalJsSlZVbTVrV|Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVU2pSV2JGcHlWMjVrVl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 7 time
  65. /(Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVbTFHYWxKc1NsWlZiVFZyV|Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVMnBTVjJKR2NIbFdNalZyVm)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 8 time
  66. /(Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZiVEZIWVd4S2MxTnNXbFppVkZaeV|Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZNbkJUVmpKS1IyTkliRmROYWxaeVZt)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 9 time
  67. /(Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFppVkVaSVdWZDRTMk14VG5OWGJGcHBWa1phZ|Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZad)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 10 time
  68. /(Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcHBWa1ZhU1ZkV1pEUlRNazE0Vkc1T1dHSkdjSEJXYTFwaF|Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcE9ZbXRLVlZadGNFdFRNVWw1Vkd0c2FWSnRVazlaVjNoaFpWWmFk)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 11 time
  69. ];
  70.  
  71. //TODO
  72. const regexEncodedPrefixNewline1 = [
  73. /(Cmh0dHA6L|Cmh0dHBzOi8)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 1 time
  74. /(Q21oMGRIQTZM|Q21oMGRIQnpPaT)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 2 time
  75. /(UTIxb01HUklRVFpN|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 3 time
  76. /(VVRJeGIwMUhVa2xSVkZwT|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 4 time
  77. /(VlZSSmVHSXdNVWhWYTJ4U1ZrWndU|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 5 time
  78. /(VmxaU1NtVkhTWGROVldoV1lUSjRVMVpyV25kV|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 6 time
  79. /(Vm14YVUxTnRWa2hUV0dST1ZsZG9WMWxVU2pSVk1WcHlWMjVrV|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 7 time
  80. /(Vm0xNFlWVXhUblJXYTJoVVYwZFNUMVpzWkc5V01XeFZVMnBTVmsxV2NIbFdNalZyV|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 8 time
  81. /(Vm0weE5GbFdWWGhVYmxKWFlUSm9WVll3WkZOVU1WcHpXa2M1VjAxWGVGWlZNbkJUVm1zeFYyTkliRmROYWxaeV|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 9 time
  82. /(Vm0wd2VFNUdiRmRXV0doVllteEtXRmxVU205V1ZsbDNXa1pPVlUxV2NIcFhhMk0xVmpBeFdHVkdXbFpOYmtKVVZtMXplRll5VGtsaVJtUk9ZV3hhZV|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 10 time
  83. /(Vm0wd2QyVkZOVWRpUm1SWFYwZG9WbGx0ZUV0WFJteFZVMjA1VjFac2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEJlRmRIVmtkWGJGcE9ZbXRLVlZadE1YcGxSbGw1Vkd0c2FWSnRVazlaVjNoaFpW|aaaa)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 newline, encoding 11 time
  84. ];
  85.  
  86. //TODO
  87. const regexEncodedPrefixNewline2 = [
  88. /(CgpodHRwOi8|CgpodHRwczov)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 1 time
  89. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 2 time
  90. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 3 time
  91. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 4 time
  92. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 5 time
  93. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 6 time
  94. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 7 time
  95. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 8 time
  96. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 9 time
  97. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 10 time
  98. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 newline, encoding 11 time
  99. ];
  100.  
  101. //TODO
  102. const regexEncodedPrefixSpace1 = [
  103. /(IGh0dHA6L|IGh0dHBzOi8)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 1 time
  104. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 2 time
  105. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 3 time
  106. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 4 time
  107. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 5 time
  108. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 6 time
  109. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 7 time
  110. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 8 time
  111. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 9 time
  112. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 10 time
  113. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 1 space, encoding 11 time
  114. ];
  115.  
  116. //TODO
  117. const regexEncodedPrefixSpace2 = [
  118. /(ICBodHRwOi8|ICBodHRwczov)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 1 time
  119. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 2 time
  120. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 3 time
  121. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 4 time
  122. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 5 time
  123. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 6 time
  124. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 7 time
  125. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 8 time
  126. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 9 time
  127. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 10 time
  128. /(|)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //with 2 space, encoding 11 time
  129. ];
  130.  
  131. //auto decoding maximum
  132. const autoDecodingMaximum = Math.min(regexEncodedPrefixDef.length, regexEncodedPrefixNewline1.length, regexEncodedPrefixNewline2.length, regexEncodedPrefixSpace1.length, regexEncodedPrefixSpace2.length);
  133.  
  134. //regex prefix - drag
  135. const regInvalid = /[^\w\+\/=]/;
  136.  
  137. //update check interval (sec, def:1 day(86400))
  138. const updateInterval = 21600;
  139.  
  140. //update chk, fail->false
  141. let updateAvailble = true;
  142.  
  143. //auto drag decoding enable status
  144. let draggableActivated = false;
  145.  
  146. //sweetalert2
  147. let modalUIEnabled = false;
  148.  
  149. //encoded link list, [uuid]: [encoded link]
  150. let encodedList = {};
  151.  
  152. //total decode count
  153. let hindex = 0;
  154.  
  155. //drag function comparison
  156. let lastSelected = document;
  157. let lastSelectedTime = Date.now();
  158.  
  159. //domain - end chk
  160. const deniedURLSuffix = ['/write', '/edit'];
  161.  
  162. //logging prefix, param
  163. const logPromptDEF = '['+GM.info.script.name+']';
  164. const logPromptDEC = '['+GM.info.script.name+'-DEC]';
  165. const logPromptUPD = '['+GM.info.script.name+'-UPD]';
  166. const logPromptPARAM = '['+GM.info.script.name+'-PAR]';
  167.  
  168. //script local parameter
  169. let localParameter = {
  170. 'lastupdate': {
  171. 'param_name': 'lastupdate',
  172. 'value': 0,
  173. 'def_value': 0,
  174. },
  175. 'basedepth': {
  176. 'param_name': 'basedepth',
  177. 'value': 3,
  178. 'def_value': 3,
  179. },
  180. 'enclinkhide': {
  181. 'param_name': 'enclinkhide',
  182. 'value': false,
  183. 'def_value': false,
  184. },
  185. 'draggable': {
  186. 'param_name': 'draggable',
  187. 'value': false,
  188. 'def_value': false,
  189. },
  190. 'updatechk': {
  191. 'param_name': 'chkupd',
  192. 'value': true,
  193. 'def_value': true,
  194. },
  195. 'extlinkwarn': {
  196. 'param_name': 'extlinkwarn',
  197. 'value': true,
  198. 'def_value': true,
  199. },
  200. 'appliedchannel': {
  201. 'param_name': 'appliedchannel',
  202. 'value': [],
  203. 'def_value': [],
  204. },
  205. 'expandmenu': {
  206. 'param_name': 'expandmenu',
  207. 'value': true,
  208. 'def_value': true,
  209. },
  210. };
  211.  
  212. //script menu structure
  213. let menuStructure = {
  214. 'basedepth': {
  215. 'param_name': localParameter.basedepth,
  216. 'name': '🎛 base64 깊이 조절하기 - 현재 값 : 알수없음',
  217. 'desc': '자동 base64 디코딩 깊이를 조절할 수 있습니다.',
  218. 'id': -1,
  219. 'func': menuFunctionBasedepth,
  220. 'visible': true,
  221. },
  222. 'enclinkhide': {
  223. 'param_name': localParameter.enclinkhide,
  224. 'name': '🔗 인코딩된 링크 [보이기/숨기기]',
  225. 'desc': '자동 base64 디코딩 전 인코딩된 링크를 항상 보이게 할지 설정할 수 있습니다.',
  226. 'id': -1,
  227. 'func': menuFunctionEnchide,
  228. 'visible': true,
  229. },
  230. 'extlinkwarn': {
  231. 'param_name': localParameter.extlinkwarn,
  232. 'name': '❗️ 외부 링크 경고 [보이기/숨기기]',
  233. 'desc': '디코딩된 링크 클릭 시 외부링크에 대한 경고 메시지 표시 여부를 설정할 수 있습니다.',
  234. 'id': -1,
  235. 'func': menuFunctionNotAvailable,
  236. 'visible': false,
  237. },
  238. 'draggable': {
  239. 'param_name': localParameter.draggable,
  240. 'name': '🖱 드래그 시 자동 디코딩 [켜기/끄기]',
  241. 'desc': '드래그 시 자동으로 드래그한 부분을 base64로 디코딩할지 설정할 수 있습니다.',
  242. 'id': -1,
  243. 'func': menuFunctionDraggable,
  244. 'visible': true,
  245. },
  246. 'appliedchannel': {
  247. 'param_name': localParameter.appliedchannel,
  248. 'name': '🏷 이 채널에서 자동 디코딩 [켜기/끄기]',
  249. 'desc': '현재 보고있는 채널에서 자동 디코딩 기능 여부를 설정할 수 있습니다.',
  250. 'id': -1,
  251. 'func': menuFunctionNotAvailable,
  252. 'visible': false,
  253. },
  254. 'updatechk': {
  255. 'param_name': localParameter.updatechk,
  256. 'name': '🔄 업데이트 알림 [켜기/끄기]',
  257. 'desc': '새 버전이 나올 시 업데이트 확인 알림을 띄울지 여부를 설정할 수 있습니다.',
  258. 'id': -1,
  259. 'func': menuFunctionUpdateCheck,
  260. 'visible': true,
  261. },
  262. 'resetdefaults': {
  263. 'param_name': null,
  264. 'name': '🛠 스크립트 기본값 초기화',
  265. 'desc': '스크립트의 사용자 설정을 초기화하고 설치 상태로 되돌립니다.',
  266. 'id': -1,
  267. 'func': menuFunctionRstDefaults,
  268. 'visible': true,
  269. },
  270.  
  271. //proto
  272. 'prototype': {
  273. 'param_name': null,
  274. 'name': '🔤 확장패널 메뉴 제목',
  275. 'desc': '확장패널 설명 내용.',
  276. 'id': -1,
  277. 'func': menuFunctionNotAvailable,
  278. 'visible': false,
  279. },
  280. //default
  281. 'expandmenu': {
  282. 'param_name': localParameter.expandmenu,
  283. 'name': '⚙️ 스크립트 메뉴 [축소/확장]',
  284. 'desc': '스크립트 설정 메뉴를 확장하거나 축소할 수 있습니다.',
  285. 'id': -1,
  286. 'func': menuFunctionChangeExpandMode,
  287. 'visible': true,
  288. },
  289. };
  290.  
  291. function getLocation(href) {
  292. var match = href.toString().match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([\/]{0,1}[^?#]*)(\?[^#]*|)(#.*|)$/);
  293. return match && {
  294. href: href,
  295. protocol: match[1],
  296. host: match[2],
  297. hostname: match[3],
  298. port: match[4],
  299. pathname: match[5],
  300. search: match[6],
  301. hash: match[7]
  302. };
  303. }
  304.  
  305. //element id - random uuid
  306. function createElemID() {
  307. return 'abad_'+self.crypto.randomUUID();
  308. }
  309.  
  310. //auto add padding - add '=' padding in base64 encoded string
  311. function base64AddPadding(str) {
  312. return str + Array((4 - str.length % 4) % 4 + 1).join('=');
  313. }
  314.  
  315. //base64 decode
  316. const Base64 = {
  317. _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  318. decode : function (input) {
  319. let output = "";
  320. let chr1, chr2, chr3;
  321. let enc1, enc2, enc3, enc4;
  322. let i = 0;
  323.  
  324. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  325.  
  326. while (i < input.length) {
  327. enc1 = this._keyStr.indexOf(input.charAt(i++));
  328. enc2 = this._keyStr.indexOf(input.charAt(i++));
  329. enc3 = this._keyStr.indexOf(input.charAt(i++));
  330. enc4 = this._keyStr.indexOf(input.charAt(i++));
  331.  
  332. chr1 = (enc1 << 2) | (enc2 >> 4);
  333. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  334. chr3 = ((enc3 & 3) << 6) | enc4;
  335.  
  336. //last bits
  337. output = output + String.fromCharCode(chr1);
  338. if (enc3 != 64) { //=
  339. output = output + String.fromCharCode(chr2);
  340. }
  341. if (enc4 != 64) { //==
  342. output = output + String.fromCharCode(chr3);
  343. }
  344. }
  345.  
  346. output = Base64._utf8_decode(output);
  347. return output;
  348. },
  349. // private method for UTF-8 decoding
  350. _utf8_decode : function (utftext) {
  351. let string = "";
  352. let i = 0;
  353. let c = 0;
  354. let c1 = 0;
  355. let c2 = 0;
  356. let c3 = 0;
  357.  
  358. while (i < utftext.length) {
  359. c = utftext.charCodeAt(i);
  360. if (c < 128) {
  361. string += String.fromCharCode(c);
  362. i++;
  363. }
  364. else if ((c > 191) && (c < 224)) {
  365. c2 = utftext.charCodeAt(i+1);
  366. string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  367. i += 2;
  368. }
  369. else {
  370. c2 = utftext.charCodeAt(i+1);
  371. c3 = utftext.charCodeAt(i+2);
  372. string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  373. i += 3;
  374. }
  375. }
  376. return string;
  377. }
  378. };
  379.  
  380. //encoded link click callback
  381. function showEncodedLink(event) {
  382. const self = event.currentTarget;
  383. //check already clicked
  384. if (encodedList.hasOwnProperty(self.id)) {
  385. window.console.log(logPromptDEC, 'show encoded link -', encodedList[self.id]);
  386. self.innerHTML = encodedList[self.id];
  387. self.style.color = 'rgb(71 88 188)';
  388. self.title = '디코딩 전 인코딩된 링크입니다, 클릭 시 내용이 복사됩니다.';
  389. delete encodedList[self.id];
  390. } else {
  391. window.console.log(logPromptDEF, 'copy link to clipboard -', self.innerHTML);
  392. try {
  393. GM.setClipboard(self.innerHTML);
  394. if (modalUIEnabled) {
  395. Swal.fire({
  396. title: logPromptDEF,
  397. text: '인코딩된 코드가 클립보드로 복사되었습니다.',
  398. icon: 'success',
  399. });
  400. } else {
  401. window.alert(logPromptDEF+'\n인코딩된 코드가 클립보드로 복사되었습니다.');
  402. }
  403. } catch (e) {
  404. window.console.warn(logPromptDEC, 'error occured link copy:', e);
  405. if (modalUIEnabled) {
  406. Swal.fire({
  407. title: logPromptDEF,
  408. text: '코드 복사 실패.',
  409. icon: 'error',
  410. });
  411. } else {
  412. window.alert(logPromptDEF+'\n코드 복사 실패.');
  413. }
  414. }
  415. }
  416. return;
  417. }
  418.  
  419. //link area
  420. function createEncodedLink(src) {
  421. return '<span style="font-size: 87.5%;color: rgb(71 188 115);">[ ' + src.toString() + ' ]</span>';
  422. }
  423.  
  424. //encoded link element
  425. function createMaskEncodedLink(src) {
  426. const uuid = createElemID();
  427. encodedList[uuid] = src;
  428. return '<span id="' + uuid.toString() + '" title="클릭 시 디코딩 전 인코딩된 링크를 표시합니다.">' + '클릭 시 인코딩된 코드 보기' + '</span>';
  429. }
  430.  
  431. //link creation
  432. function createLink(src, index, url, depth, hidelink = false) {
  433. //n번째 링크 (base64 깊이: 0) [ ABCDEF= / 클릭시 원본~ ]
  434. return '<a href="'+url+'" title="'+url+' (새 창으로 열기)" target="_blank" rel="external nofollow noopener noreferrer">'+index.toString()+'번째 링크 (base64 깊이: '+depth.toString()+') <span style="font-size: 77.5%;">('+getLocation(url).hostname+')</span></a> '+(hidelink?createEncodedLink(createMaskEncodedLink(src)):createEncodedLink(src))+'';
  435. }
  436.  
  437. //decode & generate
  438. function replacerGen(numIter) {
  439. return function(source) {
  440. try {
  441. let rstring = ""; //return msg
  442. window.console.log('\n'+logPromptDEC,'No.',(hindex+1),'encoded link:\n', source.toString()); //source
  443.  
  444. //decode
  445. let converted = Base64.decode(base64AddPadding(source));
  446. //attempt to decode nested base64 encoded string
  447. for (let i=0; i<numIter; i++) {
  448. converted = Base64.decode(base64AddPadding(converted));
  449. }
  450. hindex++;
  451.  
  452. //remove invalid string - �
  453. converted = decodeURI(encodeURI(converted).replaceAll('%00', ''));
  454. window.console.log(logPromptDEC,'No.',hindex,'decode completed (depth:',numIter+1,'):\n',converted.toString()); //converted
  455.  
  456. //trim
  457. converted = converted.trim();
  458.  
  459. //split by new line
  460. converted = converted.split(/\r?\n/);
  461. //single component
  462. if (converted.length == 2 && converted[converted.length-1] == '') {
  463. rstring += createLink(source, hindex, converted[0], numIter+1, !localParameter.enclinkhide.value);
  464. //multiple component
  465. } else if (converted.length > 1) {
  466. rstring += createEncodedLink(localParameter.enclinkhide.value?source.toString():createMaskEncodedLink(source.toString()));
  467.  
  468. let nindex = 1;
  469. const hindexPrev = hindex;
  470. converted.forEach(function(i) {
  471. if (i != '') {
  472. rstring += '<br><span style="margin-left:2px;">└ </span>' + createLink('<span style="color: rgb(71 188 115);" title="자동으로 분할된 '+nindex.toString()+'번째 링크입니다.">링크 자동 분할 : '+nindex.toString()+'번째</span>', hindex, i, numIter+1);
  473. hindex++;
  474. nindex++;
  475. }
  476. });
  477. //apply last components
  478. hindex--;
  479. nindex--;
  480.  
  481. window.console.log(logPromptDEC,'No.',hindexPrev,'- splitted total :', nindex);
  482. rstring = '<span style="color: rgb(232 62 140);"><b><i>분할된 링크 총 '+nindex.toString()+'개</i></b></span> ' + rstring;
  483. } else rstring += createLink(source, hindex, converted, numIter+1, !localParameter.enclinkhide.value);
  484. return rstring;
  485. } catch(e) {
  486. window.console.warn('\n'+logPromptDEC,'error occured during decoding:', e);
  487. window.console.warn(logPromptDEC,'base64 decode fail:', source);
  488. }
  489. return '<span style="color: rgb(255 0 0);">[ base64 변환 실패: '+source.toString()+' ]</span>';
  490. };
  491. }
  492.  
  493. //user drag event
  494. function selClicked(event) {
  495. const sel = document.getSelection().toString();
  496. if (!sel.match(regInvalid) && sel.length >= 10 && lastSelectedTime + 200 < Date.now()) {
  497. try {
  498. window.console.log(logPromptDEC,'live match -',sel.toString());
  499. let converted = decodeURI(encodeURI(Base64.decode(base64AddPadding(sel))).replaceAll('%00', ''));
  500. window.console.log(logPromptDEC,'converted -',converted.toString());
  501. this.innerHTML = this.innerHTML.replace(sel, converted)+' ';
  502. } catch (e) {
  503. return;
  504. } finally {
  505. this.removeEventListener('click', selClicked);
  506. }
  507. }
  508. }
  509.  
  510. //user drag activate
  511. function activateDragDecoding() {
  512. if (draggableActivated) {
  513. window.console.log(logPromptDEF,'USR-Drag already enabled.');
  514. return;
  515. }
  516. draggableActivated = true;
  517. window.console.log(logPromptDEF,'USR-Drag enabled.');
  518. document.addEventListener('selectionchange', function() {
  519. let sel = document.getSelection().anchorNode;
  520. if (sel) {
  521. sel = sel.parentElement;
  522. if (sel != lastSelected) {
  523. lastSelected.removeEventListener('click', selClicked);
  524. sel.addEventListener('click', selClicked);
  525. lastSelected = sel;
  526. lastSelectedTime = Date.now();
  527. }
  528. }
  529. });
  530. }
  531.  
  532. //update check
  533. function checkForUpdate() {
  534. if (!updateAvailble || !localParameter.updatechk.value) {
  535. window.console.log(logPromptUPD,'updchk skipped.');
  536. return;
  537. }
  538. const currentTime = Math.floor(new Date().getTime() / 1000);
  539. if (currentTime - localParameter.lastupdate.value < updateInterval) {
  540. window.console.log(logPromptUPD,'updchk already done in '+updateInterval+' sec.. skip updchk');
  541. return;
  542. }
  543. try {
  544. GM.setValue(localParameter.lastupdate.param_name, currentTime);
  545. } catch(e) {
  546. window.console.error(logPromptUPD,'last upd time write fail -', e);
  547. return;
  548. }
  549.  
  550. window.console.log(logPromptUPD,'checking for update...');
  551.  
  552. const svrMetadataLink = 'https://update.greasyfork.org/scripts/482577/Arca%20base64%20autodecoder.meta.js';
  553. const scriptLink = 'https://greasyfork.org/ko/scripts/482577-arca-base64-autodecoder';
  554. fetch(svrMetadataLink)
  555. .then(response => response.text())
  556. .then(data => {
  557. //extract version from greaskyfork script
  558. const match = data.match(/@version\s+(\d+\.\d+)/);
  559. if (match) {
  560. const tar_version = parseFloat(match[1]);
  561. const cur_version = parseFloat(GM.info.script.version);
  562.  
  563. const openUpdateLink = () => {
  564. window.console.log(logPromptUPD,'opening source url..');
  565. if(window.open(scriptLink) == null) {
  566. window.console.log(logPromptUPD,'popup block detected..');
  567. if (modalUIEnabled) {
  568. Swal.fire({
  569. title: logPromptDEF,
  570. html: '<b>팝업 차단</b>이 설정된 것으로 보입니다.<br>차단을 해제해주세요..',
  571. icon: 'warning',
  572. timer: 15000,
  573. timerProgressBar: true,
  574. toast: true,
  575. });
  576. } else {
  577. window.alert(logPromptDEF+'\n팝업 차단이 설정된 것으로 보입니다, 차단을 해제해주세요..');
  578. }
  579. } else {
  580. if (modalUIEnabled) {
  581. Swal.fire({
  582. title: logPromptDEF,
  583. html: '<i>업데이트 후 새로고침해야 적용됩니다.</i>',
  584. icon: 'info',
  585. timer: 15000,
  586. timerProgressBar: true,
  587. toast: true,
  588. });
  589. } else {
  590. window.alert(logPromptDEF+'\n업데이트 후 새로고침해야 적용됩니다.');
  591. }
  592. }
  593. };
  594.  
  595. //new version detected
  596. if (tar_version > cur_version) {
  597. window.console.log(logPromptUPD,'new version available. ('+cur_version+' -> '+tar_version+')');
  598.  
  599.  
  600. if (modalUIEnabled) {
  601. //y/n dialog
  602. Swal.fire({
  603. title: logPromptDEF,
  604. html: '<strong>새로운 버전이 감지되었습니다. 업데이트를 권장합니다.</strong><br>( 기존버전 : '+cur_version+', 새로운 버전 : '+tar_version+' )<br>(변경사항은 아카라이브 게시글을 참고해주세요.)<br><br><i>"알림 끄기"를 누르면 앞으로 업데이트 알림을 띄우지 않습니다.</i>',
  605. icon: 'info',
  606. showDenyButton: true,
  607. confirmButtonColor: '#3085d6',
  608. denyButtonColor: '#d33',
  609. confirmButtonText: '업데이트',
  610. denyButtonText: '알림 끄기',
  611. timer: 20000,
  612. timerProgressBar: true,
  613. didOpen: (modal) => {
  614. modal.onmouseenter = Swal.stopTimer;
  615. modal.onmouseleave = Swal.resumeTimer;
  616. }
  617. }).then((result) => {
  618. if (result.isConfirmed) {
  619.  
  620. //get extension env
  621. if (!GM.info.scriptWillUpdate) {
  622. window.console.log(logPromptUPD,'extension not allowed auto update..');
  623. Swal.fire({
  624. title: logPromptDEF,
  625. html: '<b>주의!</b><br>스크립트 내용 변경 등으로 인해 확장프로그램 내 자동 업데이트가 꺼져있는 것 같습니다.<br>업데이트 시 기존 스크립트에 덮어쓰게 되어 기존 내용이 손실될 수 있습니다.<br>이 점 확인 후 업데이트 바랍니다.<br><br><i>(계속하려면 확인, 취소하려면 취소를 눌러주세요.)</i>',
  626. icon: 'warning',
  627. showCancelButton: true,
  628. confirmButtonColor: '#3085d6',
  629. cancelButtonColor: '#d33',
  630. confirmButtonText: '확인',
  631. cancelButtonText: '취소',
  632. timer: 10000,
  633. timerProgressBar: true,
  634. didOpen: (modal) => {
  635. modal.onmouseenter = Swal.stopTimer;
  636. modal.onmouseleave = Swal.resumeTimer;
  637. }
  638. }).then((result) => {
  639. if (result.isConfirmed) {
  640. openUpdateLink();
  641. } else {
  642. window.console.log(logPromptUPD,"user canceled.");
  643. }
  644. });
  645. } else {
  646. openUpdateLink();
  647. }
  648. } else if (result.isDenied){
  649. window.console.log(logPromptPARAM,'updatechk change',true.toString(),'to',false.toString());
  650. try {
  651. GM.setValue(localParameter.updatechk.param_name, false);
  652. localParameter.updatechk.value = false;
  653. window.console.log(logPromptPARAM,"updatechk change successful");
  654. menuStructureUpdate();
  655. Swal.fire({
  656. icon: 'success',
  657. title: logPromptDEF,
  658. text: '앞으로 업데이트 알림을 띄우지 않습니다.',
  659. toast: true,
  660. position: 'center',
  661. timer: 3000,
  662. timerProgressBar: true,
  663. confirmButtonText: '확인',
  664. });
  665. } catch(e) {
  666. localParameter.updatechk.value = true;
  667. window.console.error(logPromptPARAM,"updatechk change fail -", e);
  668. Swal.fire({
  669. title: logPromptDEF,
  670. html: '파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..',
  671. icon: 'error',
  672. });
  673. }
  674. } else {
  675. window.console.log(logPromptUPD,"time out");
  676. }
  677. });
  678. } else {
  679. //y/n dialog
  680. if (window.confirm(logPromptDEF+'\n새로운 버전이 감지되었습니다. 업데이트를 권장합니다.\n( 기존버전 : '+cur_version+', 새로운 버전 : '+tar_version+' )\n(변경사항은 아카라이브 게시글을 참고해주세요.)\n\n취소를 누르면 앞으로 업데이트 알림을 띄우지 않습니다.')) {
  681. //get extension env
  682. if (!GM.info.scriptWillUpdate) {
  683. window.console.log(logPromptUPD,'extension not allowed auto update..');
  684. if (window.confirm(logPromptDEF+'\n주의! 스크립트 내용 변경 등으로 인해 확장프로그램 내 자동 업데이트가 꺼져있는 것 같습니다.\n업데이트 시 기존 스크립트에 덮어쓰게 되어 기존 내용이 손실될 수 있습니다.\n이 점 확인 후 업데이트 바랍니다.\n\n(계속하려면 확인, 취소하려면 취소를 눌러주세요.)')) {
  685. openUpdateLink();
  686. } else {
  687. window.console.log(logPromptUPD,"user canceled.");
  688. }
  689. } else {
  690. openUpdateLink();
  691. }
  692. } else {
  693. window.console.log(logPromptPARAM,'updatechk change',true.toString(),'to',false.toString());
  694. try {
  695. GM.setValue(localParameter.updatechk.param_name, false);
  696. localParameter.updatechk.value = false;
  697. window.console.log(logPromptPARAM,"updatechk change successful");
  698. menuStructureUpdate();
  699. window.alert(logPromptDEF+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
  700. } catch(e) {
  701. localParameter.updatechk.value = true;
  702. window.console.error(logPromptPARAM,"updatechk change fail -", e);
  703. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  704. }
  705. }
  706. }
  707. } else {
  708. window.console.log(logPromptUPD,'latest version', cur_version, 'detected. (eth:',tar_version,')');
  709. }
  710. } else {
  711. window.console.error(logPromptUPD,'unable to extract version..');
  712. }
  713. })
  714. .catch(error => {
  715. updateAvailble = false;
  716. window.console.error(logPromptUPD,'link unreachable.. -', error);
  717. //next try
  718. try {
  719. GM.setValue(localParameter.updatechk.param_name, true);
  720. } catch (_) {}
  721. });
  722. updateAvailble = false;
  723. }
  724.  
  725. //menu update
  726. function menuStructureUpdate(fistRun = false) {
  727. //pre process
  728. localParameter.basedepth.value = localParameter.basedepth.value > autoDecodingMaximum ? autoDecodingMaximum : localParameter.basedepth.value;
  729.  
  730. //update menu name
  731. menuStructure.basedepth.name = '🎛 base64 깊이 조절하기 - 현재 값 : '+localParameter.basedepth.value+'회';
  732. menuStructure.enclinkhide.name = '🔗 인코딩된 링크 '+(localParameter.enclinkhide.value?'숨기기':'보이기');
  733. menuStructure.draggable.name = '🖱 드래그 시 자동 디코딩 '+(localParameter.draggable.value?'끄기':'켜기');
  734. menuStructure.updatechk.name = '🔄 업데이트 알림 '+(localParameter.updatechk.value?'끄기':'켜기');
  735.  
  736. menuStructure.extlinkwarn.name = '❗️ 외부 링크 경고 '+(localParameter.extlinkwarn.value?'숨기기':'보이기');
  737. menuStructure.appliedchannel.name = '🏷 이 채널에서 자동 디코딩 [켜기/끄기]';
  738.  
  739. menuStructure.expandmenu.name = '⚙️ 스크립트 메뉴 '+(localParameter.expandmenu.value?'축소':'확장');
  740.  
  741. //remove exist menu cmd
  742. if (!fistRun) {
  743. Object.keys(menuStructure).forEach(function(i) {
  744. try {
  745. GM.unregisterMenuCommand(menuStructure[i].id);
  746. } catch(_) {}
  747. });
  748. }
  749. //monkey menu cmd register
  750. try {
  751. //all menu expanded
  752. if(localParameter.expandmenu.value) {
  753. Object.keys(menuStructure).forEach(function(i) {
  754. if (menuStructure[i].visible) {
  755. menuStructure[i].id = GM.registerMenuCommand(menuStructure[i].name, menuStructure[i].func, {title:menuStructure[i].desc});
  756. } else {
  757. //if invisible -> use default parameter
  758. if (localParameter.hasOwnProperty(i)) {
  759. localParameter[i].value = localParameter[i].def_value;
  760. }
  761. }
  762. });
  763. //simple menu
  764. } else {
  765. menuStructure.expandmenu.id = GM.registerMenuCommand(menuStructure.expandmenu.name, menuStructure.expandmenu.func, {title:menuStructure.expandmenu.desc});
  766. }
  767. window.console.log(logPromptPARAM,'ext opt pannel',(fistRun?'registered':'reloaded'));
  768. } catch(e) {
  769. window.console.error(logPromptPARAM,'err - ext opt pannel',(fistRun?'register':'reload'),'- ', e);
  770. Object.keys(menuStructure).forEach(function(i) {
  771. try {
  772. GM.unregisterMenuCommand(menuStructure[i].id);
  773. } catch(_) {}
  774. });
  775. try { GM.registerMenuCommand('ⓘ 메뉴 추가 실패, 브라우저 로그 참고', () => {
  776. if (modalUIEnabled) {
  777. Swal.fire({
  778. title: logPromptDEF,
  779. html: '메뉴 추가 도중 문제가 발생했습니다.<br><i>브라우저 로그를 확인해주세요..</i>',
  780. icon: 'error',
  781. timer: 5000,
  782. timerProgressBar: true,
  783. });
  784. } else {
  785. window.alert(logPromptDEF+'\n메뉴 추가 도중 문제가 발생했습니다, 브라우저 로그를 확인해주세요..');
  786. }
  787. }); } catch(_) {}
  788. }
  789. }
  790.  
  791. function menuFuncSubPageReload(showmsg) {
  792. if (modalUIEnabled) {
  793. Swal.fire({
  794. title: logPromptDEF,
  795. html: ((showmsg==undefined)?'':(showmsg+'<br><br>'))+'> 반영을 위해 사이트 새로고침이 필요합니다, 사이트를 새로고침할까요?',
  796. icon: 'info',
  797. showCancelButton: true,
  798. confirmButtonColor: '#3085d6',
  799. confirmButtonText: '새로고침',
  800. cancelButtonText: '취소',
  801. }).then((result) => {
  802. if (result.isConfirmed) {
  803. window.location.reload(true);
  804. } else {
  805. window.console.log(logPromptDEF, 'page reload canceled');
  806. }
  807. });
  808. } else {
  809. if(window.confirm(logPromptDEF+'\n'+((showmsg==undefined)?'':(showmsg+'\n\n'))+'> 반영을 위해 사이트 새로고침이 필요합니다, 사이트를 새로고침할까요?')) {
  810. window.location.reload(true);
  811. }
  812. }
  813. }
  814.  
  815. function menuFunctionBasedepth() {
  816. menuStructureUpdate();
  817. const previousValue = localParameter.basedepth.value;
  818. const str_common_1 = ' ( 지정 가능한 범위: 1~'+autoDecodingMaximum.toString()+' )';
  819.  
  820. if (modalUIEnabled) {
  821. Swal.fire({
  822. title: logPromptDEF,
  823. icon: "question",
  824. input: "range",
  825. html: 'Base64 자동 디코딩 중첩 횟수를 얼마로 지정할까요?<br><i>(인코딩을 인코딩한 것을 여러번 반복한걸 자동으로 풀어냅니다.)</i><br>현재 값: '+previousValue.toString()+'회,'+(previousValue == 3 ? '' : ' 기본값: 3회,')+str_common_1,
  826. inputAttributes: {
  827. min: "1",
  828. max: autoDecodingMaximum.toString(),
  829. step: "1"
  830. },
  831. footer: '<i>(값을 너무 크게 지정하면 컴퓨터 성능에 영향을 줄 수 있습니다.)</i>',
  832. inputValue: previousValue,
  833. showCancelButton: true,
  834. confirmButtonColor: '#3085d6',
  835. confirmButtonText: '변경',
  836. cancelButtonText: '취소',
  837. inputValidator: (value) => {
  838. return new Promise((resolve) => {
  839. if (value == previousValue) {
  840. resolve('기존값과 동일합니다, 현재 값: '+previousValue+'회');
  841. } else {
  842. resolve();
  843. }
  844. });
  845. },
  846. }).then((result) => {
  847. if (result.isConfirmed) {
  848. const targetValue = parseInt(result.value);
  849. window.console.log(logPromptPARAM,'basedepth change',previousValue.toString(),'to',targetValue.toString());
  850. localParameter.basedepth.value = targetValue;
  851. try {
  852. GM.setValue(localParameter.basedepth.param_name, targetValue);
  853. window.console.log(logPromptPARAM,"basedepth change successful");
  854. menuFuncSubPageReload('값이 '+previousValue.toString()+'에서 '+targetValue.toString()+'으로 변경이 완료되었습니다.');
  855. } catch(e) {
  856. localParameter.basedepth.value = previousValue;
  857. window.console.error(logPromptPARAM,"basedepth change fail -", e);
  858. Swal.fire({
  859. title: logPromptDEF,
  860. html: '파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..',
  861. icon: 'error',
  862. });
  863. } finally {
  864. menuStructureUpdate();
  865. }
  866. } else {
  867. window.console.log(logPromptDEF,'basedepth change canceled.');
  868. }
  869. });
  870. } else {
  871. while (true) {
  872. const input = window.prompt(logPromptDEF+'\nBase64 자동 디코딩 중첩 횟수를 얼마로 지정할까요?\n(인코딩을 인코딩한 것을 여러번 반복한걸 자동으로 풀어냅니다.)\n현재 값: '+previousValue.toString()+'회,'+(previousValue == 3 ? '' : ' 기본값: 3회,')+str_common_1+'\n\n(값을 너무 크게 지정하면 컴퓨터 성능에 영향을 줄 수 있습니다.)', previousValue);
  873. if (input == null) {
  874. window.console.log(logPromptDEF,'basedepth change canceled.');
  875. break;
  876. }
  877. if (!isNaN(input)) {
  878. const targetValue = parseInt(input);
  879. if (targetValue == previousValue) {
  880. window.alert(logPromptDEF+'\n동일한 값을 입력했습니다, 현재 값: '+previousValue+'회');
  881. } else if (targetValue >= 1 && targetValue <= autoDecodingMaximum) {
  882. window.console.log(logPromptPARAM,'basedepth change',previousValue.toString(),'to',targetValue.toString());
  883. localParameter.basedepth.value = targetValue;
  884. try {
  885. GM.setValue(localParameter.basedepth.param_name, targetValue);
  886. window.console.log(logPromptPARAM,"basedepth change successful");
  887. menuFuncSubPageReload('값이 '+previousValue.toString()+'에서 '+targetValue.toString()+'으로 변경이 완료되었습니다.');
  888. } catch(e) {
  889. localParameter.basedepth.value = previousValue;
  890. window.console.error(logPromptPARAM,"basedepth change fail -", e);
  891. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  892. } finally {
  893. menuStructureUpdate();
  894. break;
  895. }
  896. } else {
  897. window.alert(logPromptDEF+'\n'+targetValue+'(으)로 설정할 수 없습니다.\n범위를 초과하였습니다..'+str_common_1);
  898. }
  899. } else {
  900. window.alert(logPromptDEF+'\n'+input+'은(는)숫자가 아닙니다.\n숫자만 입력해주세요..'+str_common_1);
  901. }
  902. }
  903. }
  904. }
  905.  
  906. function menuFunctionEnchide() {
  907. menuStructureUpdate();
  908. const currentState = localParameter.enclinkhide.value;
  909. if (modalUIEnabled) {
  910. Swal.fire({
  911. title: logPromptDEF,
  912. html: '<b>디코딩 시 인코딩된 링크를 '+(currentState?'숨기시':'표시하')+'겠습니까?</b><br><br><i>(앞으로 디코딩 전 인코딩된 링크를<br>"'+(currentState?'클릭 시 기존링크 보기':'aHR0cHM6Ly9hcmNhLmx..')+'"와 같은 형태로 보여줍니다.)</i>',
  913. icon: 'question',
  914. showCancelButton: true,
  915. confirmButtonColor: '#3085d6',
  916. cancelButtonColor: '#d33',
  917. confirmButtonText: '네',
  918. cancelButtonText: '취소',
  919. }).then((result) => {
  920. if (result.isConfirmed) {
  921. const targetState = !currentState;
  922. window.console.log(logPromptPARAM,'enchide change',currentState.toString(),'to',targetState.toString());
  923. localParameter.enclinkhide.value = targetState;
  924. try {
  925. GM.setValue(localParameter.enclinkhide.param_name, targetState);
  926. window.console.log(logPromptPARAM,"updatechk change successful");
  927. Swal.fire({
  928. icon: 'success',
  929. title: logPromptDEF,
  930. text: '앞으로 인코딩된 링크를 '+(targetState?'표시합':'숨깁')+'니다.',
  931. toast: true,
  932. position: 'center',
  933. timer: 1500,
  934. timerProgressBar: true,
  935. confirmButtonText: '확인',
  936. });
  937. } catch(e) {
  938. localParameter.enclinkhide.value = currentState;
  939. window.console.error(logPromptPARAM,"enchide change fail -", e);
  940. Swal.fire({
  941. title: logPromptDEF,
  942. html: '파라미터 변경 중 문제 발생.<br><i>브라우저 로그를 확인해주세요..</i>',
  943. icon: 'error',
  944. });
  945. } finally {
  946. menuStructureUpdate();
  947. }
  948. } else {
  949. window.console.log(logPromptDEF,'enchide change canceled.');
  950. }
  951. });
  952. } else {
  953. if (window.confirm(logPromptDEF+'\n디코딩 시 인코딩된 링크를 '+(currentState?'숨기시':'표시하')+'겠습니까?\n\n(앞으로 디코딩 전 인코딩된 링크를\n"'+(currentState?'클릭 시 기존링크 보기':'aHR0cHM6Ly9hcmNhLmx..')+'"와 같은 형태로 보여줍니다.)')) {
  954. const targetState = !currentState;
  955. window.console.log(logPromptPARAM,'enchide change',currentState.toString(),'to',targetState.toString());
  956. localParameter.enclinkhide.value = targetState;
  957. try {
  958. GM.setValue(localParameter.enclinkhide.param_name, targetState);
  959. window.console.log(logPromptPARAM,"updatechk change successful");
  960. if (targetState) {
  961. menuFuncSubPageReload('앞으로 인코딩된 링크를 표시합니다.');
  962. } else {
  963. window.alert(logPromptDEF+'\n앞으로 인코딩된 링크를 숨깁니다.');
  964. }
  965. } catch(e) {
  966. localParameter.enclinkhide.value = currentState;
  967. window.console.error(logPromptPARAM,"enchide change fail -", e);
  968. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  969. } finally {
  970. menuStructureUpdate();
  971. }
  972. } else {
  973. window.console.log(logPromptDEF,'enchide change canceled.');
  974. }
  975. }
  976. }
  977.  
  978. function menuFunctionDraggable() {
  979. menuStructureUpdate();
  980. const currentState = localParameter.draggable.value;
  981. if (modalUIEnabled) {
  982. Swal.fire({
  983. title: logPromptDEF,
  984. html: '<b>드래그 시 자동 디코딩을 '+(currentState?'비':'')+'활성화 하시겠습니까?</b><br><br><i>(앞으로 인코딩된 부분을 드래그'+(currentState?'해도 자동으로 디코딩되지 않습':' 시 Base64로 인코딩된것으로 판단 되면 자동으로 디코딩을 시도합')+'니다.)</i>'+(currentState?'':'<br><br><i>(이 기능은 작동이 불안정할 수 있습니다.)</i>'),
  985. icon: 'question',
  986. showCancelButton: true,
  987. confirmButtonColor: '#3085d6',
  988. cancelButtonColor: '#d33',
  989. confirmButtonText: '네',
  990. cancelButtonText: '취소',
  991. }).then((result) => {
  992. if (result.isConfirmed) {
  993. const targetState = !currentState;
  994. window.console.log(logPromptPARAM,'draggable change',currentState.toString(),'to',targetState.toString());
  995. localParameter.draggable.value = targetState;
  996. try {
  997. GM.setValue(localParameter.draggable.param_name, targetState);
  998. window.console.log(logPromptPARAM,"draggable change successful");
  999. if (targetState) {
  1000. try {
  1001. activateDragDecoding();
  1002. Swal.fire({
  1003. icon: 'success',
  1004. title: logPromptDEF,
  1005. text: '앞으로 드래그 시 자동 디코딩을 진행합니다.',
  1006. toast: true,
  1007. position: 'center',
  1008. timer: 1500,
  1009. timerProgressBar: true,
  1010. confirmButtonText: '확인',
  1011. });
  1012. } catch(e) {
  1013. window.console.error(logPromptDEF,"draggable activate fail -", e);
  1014. Swal.fire({
  1015. title: logPromptDEF,
  1016. html: '드래그 시 자동 디코딩 활성화 중 문제가 발생했습니다.<br><i>브라우저 로그를 확인해주세요..</i><br><br><i>새로고침이 필요합니다..</i>',
  1017. icon: 'error',
  1018. });
  1019. }
  1020. } else {
  1021. menuFuncSubPageReload('앞으로 드래그 해도 반응하지 않습니다.');
  1022. }
  1023. } catch(e) {
  1024. localParameter.draggable.value = currentState;
  1025. window.console.error(logPromptPARAM,"draggable change fail -", e);
  1026. Swal.fire({
  1027. title: logPromptDEF,
  1028. html: '파라미터 변경 중 문제 발생.<br><i>브라우저 로그를 확인해주세요..</i>',
  1029. icon: 'error',
  1030. });
  1031. } finally {
  1032. menuStructureUpdate();
  1033. }
  1034. } else {
  1035. window.console.log(logPromptDEF,'draggable change canceled.');
  1036. }
  1037. });
  1038. } else {
  1039. if (window.confirm(logPromptDEF+'\n드래그 시 자동 디코딩을 '+(currentState?'비':'')+'활성화 하시겠습니까?\n\n(앞으로 인코딩된 부분을 드래그'+(currentState?'해도 자동으로 디코딩되지 않습':' 시 Base64로 인코딩된것으로\n판단 되면 자동으로 디코딩을 시도합')+'니다.)\n\n(이 기능은 작동이 불안정할 수 있습니다.)')) {
  1040. const targetState = !currentState;
  1041. window.console.log(logPromptPARAM,'draggable change',currentState.toString(),'to',targetState.toString());
  1042. localParameter.draggable.value = targetState;
  1043. try {
  1044. GM.setValue(localParameter.draggable.param_name, targetState);
  1045. window.console.log(logPromptPARAM,"draggable change successful");
  1046. if (targetState) {
  1047. try {
  1048. activateDragDecoding();
  1049. window.alert(logPromptDEF+'\n앞으로 드래그 시 자동 디코딩을 진행합니다.');
  1050. } catch(e) {
  1051. window.console.error(logPromptDEF,"draggable activate fail -", e);
  1052. window.alert(logPromptDEF+'\n드래그 시 자동 디코딩 활성화 중 문제가 발생했습니다, 브라우저 로그를 확인해주세요..\n새로고침이 필요합니다..');
  1053. }
  1054. } else {
  1055. menuFuncSubPageReload('앞으로 드래그 해도 반응하지 않습니다.');
  1056. }
  1057. } catch(e) {
  1058. localParameter.draggable.value = currentState;
  1059. window.console.error(logPromptPARAM,"draggable change fail -", e);
  1060. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  1061. } finally {
  1062. menuStructureUpdate();
  1063. }
  1064. } else {
  1065. window.console.log(logPromptDEF,'draggable change canceled.');
  1066. }
  1067. }
  1068. }
  1069.  
  1070. function menuFunctionUpdateCheck() {
  1071. menuStructureUpdate();
  1072. const currentState = localParameter.updatechk.value;
  1073. if (modalUIEnabled) {
  1074. Swal.fire({
  1075. title: logPromptDEF,
  1076. html: '<b>업데이트 알림을 '+(currentState?'끄':'켜')+'시겠습니까?</b><br><br><i>(앞으로 업데이트가 있'+(currentState?'어도 알려주지 않습':'으면 자동으로 알려줍')+'니다.)</i>',
  1077. icon: 'question',
  1078. showCancelButton: true,
  1079. confirmButtonColor: '#3085d6',
  1080. cancelButtonColor: '#d33',
  1081. confirmButtonText: '네',
  1082. cancelButtonText: '취소',
  1083. }).then((result) => {
  1084. if (result.isConfirmed) {
  1085. const targetState = !currentState;
  1086. window.console.log(logPromptPARAM,'updatechk change',currentState.toString(),'to',targetState.toString());
  1087. localParameter.updatechk.value = targetState;
  1088. try {
  1089. GM.setValue(localParameter.updatechk.param_name, targetState);
  1090. window.console.log(logPromptPARAM,"updatechk change successful");
  1091. Swal.fire({
  1092. icon: 'success',
  1093. title: logPromptDEF,
  1094. text: '앞으로 업데이트'+(targetState?'가 존재하면':'')+' 알림을 띄'+(targetState?'웁':'우지 않습')+'니다.',
  1095. toast: true,
  1096. position: 'center',
  1097. timer: 1500,
  1098. timerProgressBar: true,
  1099. confirmButtonText: '확인',
  1100. });
  1101. } catch(e) {
  1102. localParameter.updatechk.value = currentState;
  1103. window.console.error(logPromptPARAM,"updatechk change fail -", e);
  1104. Swal.fire({
  1105. title: logPromptDEF,
  1106. html: '파라미터 변경 중 문제 발생.<br><i>브라우저 로그를 확인해주세요..</i>',
  1107. icon: 'error',
  1108. });
  1109. } finally {
  1110. menuStructureUpdate();
  1111. }
  1112. } else {
  1113. window.console.log(logPromptDEF,'updatechk change canceled.');
  1114. }
  1115. });
  1116. } else {
  1117. if (window.confirm(logPromptDEF+'\n업데이트 알림을 '+(currentState?'끄':'켜')+'시겠습니까?\n\n(앞으로 업데이트가 있'+(currentState?'어도 알려주지 않습':'으면 자동으로 알려줍')+'니다.)')) {
  1118. const targetState = !currentState;
  1119. window.console.log(logPromptPARAM,'updatechk change',currentState.toString(),'to',targetState.toString());
  1120. localParameter.updatechk.value = targetState;
  1121. try {
  1122. GM.setValue(localParameter.updatechk.param_name, targetState);
  1123. window.console.log(logPromptPARAM,"updatechk change successful");
  1124. window.alert(logPromptDEF+'\n앞으로 업데이트'+(targetState?'가 존재하면':'')+' 알림을 띄'+(targetState?'웁':'우지 않습')+'니다.');
  1125. } catch(e) {
  1126. localParameter.updatechk.value = currentState;
  1127. window.console.error(logPromptPARAM,"updatechk change fail -", e);
  1128. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  1129. } finally {
  1130. menuStructureUpdate();
  1131. }
  1132. } else {
  1133. window.console.log(logPromptDEF,'updatechk change canceled.');
  1134. }
  1135. }
  1136. }
  1137.  
  1138. function menuFunctionChangeExpandMode() {
  1139. menuStructureUpdate();
  1140. const currentState = localParameter.expandmenu.value;
  1141. if (modalUIEnabled) {
  1142. Swal.fire({
  1143. title: logPromptDEF,
  1144. html: '<b>메뉴에 나타나는 항목을 '+(currentState?'줄일':'늘릴')+'까요?</b><br><br><i>(앞으로 세부설정 메뉴가 '+(currentState?'숨겨':'보여')+'집니다.)</i>',
  1145. icon: 'question',
  1146. showCancelButton: true,
  1147. confirmButtonColor: '#3085d6',
  1148. cancelButtonColor: '#d33',
  1149. confirmButtonText: '네',
  1150. cancelButtonText: '취소',
  1151. }).then((result) => {
  1152. if (result.isConfirmed) {
  1153. const targetState = !currentState;
  1154. window.console.log(logPromptPARAM,'menuexpand change',currentState.toString(),'to',targetState.toString());
  1155. localParameter.expandmenu.value = targetState;
  1156. try {
  1157. GM.setValue(localParameter.expandmenu.param_name, targetState);
  1158. window.console.log(logPromptPARAM,"menuexpand change successful");
  1159. Swal.fire({
  1160. icon: 'success',
  1161. title: logPromptDEF,
  1162. text: '앞으로 세부설정 메뉴가 '+(targetState?'보여':'숨겨')+'집니다.',
  1163. toast: true,
  1164. position: 'center',
  1165. timer: 1500,
  1166. timerProgressBar: true,
  1167. confirmButtonText: '확인',
  1168. });
  1169. } catch(e) {
  1170. localParameter.expandmenu.value = currentState;
  1171. window.console.error(logPromptPARAM,"menuexpand change fail -", e);
  1172. Swal.fire({
  1173. title: logPromptDEF,
  1174. html: '파라미터 변경 중 문제 발생.<br><i>브라우저 로그를 확인해주세요..</i>',
  1175. icon: 'error',
  1176. });
  1177. } finally {
  1178. menuStructureUpdate();
  1179. }
  1180. } else {
  1181. window.console.log(logPromptDEF,'menuexpand change canceled.');
  1182. }
  1183. });
  1184. } else {
  1185. if (window.confirm(logPromptDEF+'\n메뉴에 나타나는 항목을 '+(currentState?'줄일':'늘릴')+'까요?\n\n(앞으로 세부설정 메뉴가 '+(currentState?'숨겨':'보여')+'집니다.)')) {
  1186. const targetState = !currentState;
  1187. window.console.log(logPromptPARAM,'menuexpand change',currentState.toString(),'to',targetState.toString());
  1188. localParameter.expandmenu.value = targetState;
  1189. try {
  1190. GM.setValue(localParameter.expandmenu.param_name, targetState);
  1191. window.console.log(logPromptPARAM,"menuexpand change successful");
  1192. window.alert(logPromptDEF+'\n앞으로 세부설정 메뉴가 '+(targetState?'보여':'숨겨')+'집니다.');
  1193. } catch(e) {
  1194. localParameter.expandmenu.value = currentState;
  1195. window.console.error(logPromptPARAM,"menuexpand change fail -", e);
  1196. window.alert(logPromptDEF+'\n파라미터 변경 중 문제 발생, 브라우저 로그를 확인해주세요..');
  1197. } finally {
  1198. menuStructureUpdate();
  1199. }
  1200. } else {
  1201. window.console.log(logPromptDEF,'menuexpand change canceled.');
  1202. }
  1203. }
  1204. }
  1205.  
  1206. function menuFunctionRstDefaults() {
  1207. menuStructureUpdate();
  1208. if (modalUIEnabled) {
  1209. Swal.fire({
  1210. title: logPromptDEF,
  1211. html: '<b>정말 스크립트 설정을 기본값으로 초기화하시겠습니까?</b><br><br><i>(초기화 완료 후 자동으로 새로고침됩니다.)</i>',
  1212. icon: 'question',
  1213. showCancelButton: true,
  1214. confirmButtonColor: '#3085d6',
  1215. cancelButtonColor: '#d33',
  1216. confirmButtonText: '네',
  1217. cancelButtonText: '취소',
  1218. }).then((result) => {
  1219. if (result.isConfirmed) {
  1220. window.console.log(logPromptPARAM, 'remove all settings..');
  1221. Swal.fire({
  1222. title: logPromptDEF,
  1223. html: "설정값을 제거중입니다, 잠시만 기다려주세요..",
  1224. didOpen: () => {
  1225. Swal.showLoading();
  1226. },
  1227. showConfirmButton: false,
  1228. allowOutsideClick: false,
  1229. allowEscapeKey: false,
  1230. allowEnterKey: false,
  1231. });
  1232. try {
  1233. for (const i of Object.keys(localParameter)) {
  1234. console.log(logPromptPARAM, 'try to remove -', localParameter[i].param_name);
  1235. GM.deleteValue(localParameter[i].param_name);
  1236. }
  1237. Swal.close();
  1238. window.console.log(logPromptPARAM, 'all parameter removed.');
  1239. Swal.fire({
  1240. title: logPromptDEF,
  1241. html: '<b>설정값이 모두 제거되었습니다.</b><br><br><i>(확인 후 현재 창이 자동으로 새로고침됩니다.)</i>',
  1242. icon: 'info',
  1243. confirmButtonColor: '#3085d6',
  1244. confirmButtonText: '확인',
  1245. didOpen: () => {
  1246. Swal.hideLoading();
  1247. },
  1248. }).then((result) => {
  1249. window.location.reload(true);
  1250. });
  1251. } catch(e) {
  1252. window.console.error(logPromptPARAM,'err - get sc parameter - ', e);
  1253. Swal.close();
  1254. Swal.fire({
  1255. title: logPromptDEF,
  1256. didOpen: () => {
  1257. Swal.hideLoading();
  1258. },
  1259. html: '<b>경고! 파라미터 초기화 도중 문제가 발생했습니다.</b><br><i>브라우저 로그를 참고해주세요..</i>',
  1260. icon: 'error',
  1261. });
  1262. }
  1263. } else {
  1264. window.console.log(logPromptDEF,'settings restore canceled.');
  1265. }
  1266. });
  1267. } else {
  1268. if (window.confirm(logPromptDEF+'\n정말 스크립트 설정을 기본값으로 초기화하시겠습니까?\n\n(초기화 완료 후 자동으로 새로고침됩니다.)')) {
  1269. try {
  1270. window.console.log(logPromptPARAM, 'remove all settings..');
  1271. for (const i of Object.keys(localParameter)) {
  1272. console.log(logPromptPARAM, 'try to remove -', localParameter[i].param_name);
  1273. GM.deleteValue(localParameter[i].param_name);
  1274. }
  1275. window.console.log(logPromptPARAM, 'all parameter removed.');
  1276. window.alert(logPromptDEF+'\n설정값이 모두 제거되었습니다.\n\n(확인 후 현재 창이 자동으로 새로고침됩니다.)');
  1277. window.location.reload(true);
  1278. } catch(e) {
  1279. window.console.error(logPromptPARAM,'err - get sc parameter - ', e);
  1280. window.alert(logPromptDEF+'\n경고! 파라미터 초기화 도중 문제가 발생했습니다. 브라우저 로그를 참고해주세요..');
  1281. }
  1282. } else {
  1283. window.console.log(logPromptDEF,'settings restore canceled.');
  1284. }
  1285. }
  1286. }
  1287.  
  1288. function menuFunctionNotAvailable() {
  1289. window.console.log(logPromptDEF,'unavailable function clicked');
  1290. if (modalUIEnabled) {
  1291. Swal.fire({
  1292. title: logPromptDEF,
  1293. html: '현재 사용할 수 없는 기능입니다..<br><br><i>(구현되지 않았거나 버그로 인해 일시적으로<br>현재버전에서 비활성화된 기능입니다.)</i>',
  1294. icon: 'error',
  1295. timer: 5000,
  1296. timerProgressBar: true,
  1297. });
  1298. } else {
  1299. window.alert(logPromptDEF+'\n현재 사용할 수 없는 기능입니다..');
  1300. }
  1301. }
  1302.  
  1303. //main
  1304. (async () => {
  1305. 'use strict';
  1306.  
  1307. //chk browser env
  1308. if (((window.navigator.language || window.navigator.userLanguage) != 'ko-KR')) {
  1309. window.console.warn('Warning! this script support only korean language..');
  1310. }
  1311.  
  1312. window.console.log(logPromptDEF,'V',GM.info.script.version,'pre processing..');
  1313.  
  1314. //Sweet Alert2 chk
  1315. if (window.Swal != undefined) {
  1316. const styleSA2 = document.createElement('style');
  1317. styleSA2.textContent = '.swal2-container { z-index: 2400; }';
  1318. document.head.appendChild(styleSA2);
  1319. modalUIEnabled = true;
  1320. window.console.log(logPromptDEF,'SA2 loaded');
  1321. }
  1322.  
  1323. //check edit mode
  1324. if (window.location.pathname.match(/\/b\/.*?\/(write|edit)/)) {
  1325. window.console.log(logPromptDEF,'write/edit mode detected, function disabled.');
  1326. try {
  1327. GM.registerMenuCommand("작성/수정 모드에서는 동작하지 않음", ()=>{
  1328. if (modalUIEnabled) {
  1329. Swal.fire({
  1330. title: logPromptDEF,
  1331. html: '작성 또는 수정모드에서는 동작하지 않습니다..',
  1332. icon: 'error',
  1333. timer: 5000,
  1334. timerProgressBar: true,
  1335. });
  1336. } else {
  1337. window.alert(logPromptDEF+'\n작성 또는 수정모드에서는 동작하지 않습니다..');
  1338. }
  1339. }, {title:'작성 또는 수정모드에서는 동작하지 않습니다.'});
  1340. } catch(_) {}
  1341. return;
  1342. }
  1343. /*
  1344. const URLSuffix = window.location.pathname.match(/([/][a-z0-9_-]*[\/]?)$/g);
  1345. if (URLSuffix != null) {
  1346. if (deniedURLSuffix.some(str => str == URLSuffix[0])) {
  1347. window.console.log(logPromptDEF,'write/edit mode detected, function disabled.');
  1348. try {GM.registerMenuCommand("작성/수정 모드에서는 동작하지 않음", ()=>{window.alert(logPromptDEF+'\n작성 또는 수정모드에서는 동작하지 않습니다..');}, {title:'작성 또는 수정모드에서는 동작하지 않습니다.'});} catch(_) {}
  1349. return;
  1350. }
  1351. }*/
  1352.  
  1353. window.console.log(logPromptDEF,'abad enabled');
  1354.  
  1355. //load parameter
  1356. try {
  1357. for (const i of Object.keys(localParameter)) {
  1358. localParameter[i].value = await GM.getValue(localParameter[i].param_name, localParameter[i].def_value);
  1359. }
  1360. window.console.log(logPromptPARAM, 'sc parameter load completed.');
  1361. } catch(e) {
  1362. window.console.error(logPromptPARAM,'err - get sc parameter - ', e);
  1363. }
  1364.  
  1365. //apply parameter and register monkey menu command
  1366. menuStructureUpdate(true);
  1367.  
  1368. //chk update
  1369. await checkForUpdate();
  1370.  
  1371. //drag auto decoding
  1372. if (localParameter.draggable.value) {
  1373. activateDragDecoding();
  1374. }
  1375.  
  1376. window.console.log(logPromptDEF,'script ready');
  1377. //main procedure
  1378.  
  1379. //article
  1380. let article = document.getElementsByClassName("article-content")[0];
  1381. if (article != undefined) {
  1382. for (let i=0; i<localParameter.basedepth.value; i++) {
  1383. article.innerHTML = article.innerHTML.replaceAll(regexEncodedPrefixDef[i], replacerGen(i));
  1384. }
  1385. } else window.console.warn(logPromptDEF,'article not found.');
  1386. const decoded_article = hindex;
  1387.  
  1388. //comment
  1389. let comments = document.getElementsByClassName("list-area");
  1390. if (article != undefined) {
  1391. if (comments.length != 0) {
  1392. for (let i=0; i<localParameter.basedepth.value; i++) {
  1393. comments[0].innerHTML = comments[0].innerHTML.replaceAll(regexEncodedPrefixDef[i], replacerGen(i));
  1394. }
  1395. }
  1396. } else window.console.warn(logPromptDEF,'comments not found.');
  1397. const decoded_comment = hindex - decoded_article;
  1398.  
  1399. //show result on article top
  1400. if (decoded_article+decoded_comment>0) {
  1401. let result = document.createElement("div");
  1402. result.id = createElemID();
  1403. result.class = 'btn';
  1404. result.style.marginTop = '10px';
  1405. result.style.marginBottom = '10px';
  1406. result.style.paddingTop = '7px';
  1407.  
  1408. let result_box = document.createElement("span");
  1409. result_box.style.border = '1px solid rgb(104 179 255)';
  1410. result_box.style.padding = '7px 15px';
  1411.  
  1412. let result_in = '<span style="color: rgb(232 62 140);">';
  1413. if (decoded_article+decoded_comment>0) {
  1414. result_in += '총 '+(decoded_article+decoded_comment)+'개의 링크가 자동 디코딩되었습니다. <span style="font-size: 75%;">( '+((decoded_article>0)?('게시글: '+decoded_article+'개'+((decoded_comment>0)?' / ':'')):'')+((decoded_comment>0)?('댓글: '+decoded_comment+'개'):'')+' )</span>';
  1415. } else {//not use
  1416. result_in += '<span style="font-size: 75%;"><i>이 게시글 또는 댓글에서 Base64로 인코딩 된 링크가 감지되지 않았습니다..</i></span>';
  1417. }
  1418. result_in += '</span>';
  1419. result_box.innerHTML = result_in;
  1420. result.appendChild(result_box);
  1421. result.appendChild(document.createElement("hr"));
  1422. article.prepend(result);
  1423. }
  1424. window.console.log(logPromptDEC,'total',hindex,'link decode task completed. (article:', decoded_article, ', comment:', decoded_comment, ')');
  1425.  
  1426. //add event listner - click, show original encoded link
  1427. if (!localParameter.enclinkhide.value) {
  1428. Object.keys(encodedList).forEach(function(i) {
  1429. document.getElementById(i).addEventListener('click', showEncodedLink); //, { once : true }
  1430. });
  1431. }
  1432.  
  1433. })();