Greasy Fork 还支持 简体中文。

Arca base64 autodecoder

auto decode Base64 encoded link in Arca.live

目前為 2024-01-24 提交的版本,檢視 最新版本

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