Arca base64 autodecoder

Arca.live Base64 auto decoder

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

  1. // ==UserScript==
  2. // @name Arca base64 autodecoder
  3. // @name:ko 아카라이브 Base64 자동 디코더
  4. // @version 1.18
  5. // @author Laria
  6. // @match https://arca.live/b/*/*
  7. // @description Arca.live Base64 auto decoder
  8. // @description:ko 아카라이브 Base64 자동 복호화 스크립트
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=arca.live
  10. // @license MIT
  11. // @encoding utf-8
  12. // @run-at document-end
  13. // @supportURL https://greasyfork.org/ko/scripts/482577-arca-base64-autodecoder
  14. // @namespace https://greasyfork.org/users/1235854
  15. // @grant GM.setValue
  16. // @grant GM.getValue
  17. // ==/UserScript==
  18.  
  19. /*
  20. * == Change log ==
  21. * 1.0 - Release
  22. * 1.1 - Invalid character update (replace -> replaceAll)
  23. * 1.11 - Improved show multiple links
  24. * 1.12 - Show Single links Bugfix
  25. * 1.13 - Bugfix 1.12
  26. * 1.14 - Base64 add padding func
  27. * 1.15 - Add annotation, display improvements
  28. * 1.16 - Display improved - CSS applied
  29. * 1.17 - var safe, max_iter defined (~7, def:3)
  30. * 1.18 - auto update check, log system
  31. */
  32.  
  33. //max attempt decode depth, defalut depth : 3
  34. //Caution! browser performance impact..
  35. var max_iter = 3;
  36.  
  37. //base64 encoded(http:/*, https:/*) string prefix
  38. const regArr = [
  39. /(aHR0cDovL|aHR0cHM6Ly)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 1 time
  40. /(YUhSMGNEb3ZM|YUhSMGNITTZMe)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 2 time
  41. /(WVVoU01HTkViM1pN|WVVoU01HTklUVFpNZ)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 3 time
  42. /(V1ZWb1UwMUhUa1ZpTTFwT|V1ZWb1UwMUhUa2xVVkZwTl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 4 time
  43. /(VjFaV2IxVXdNVWhVYTFacFRURndU|VjFaV2IxVXdNVWhVYTJ4VlZrWndUb)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 5 time
  44. /(VmpGYVYySXhWWGROVldoVllURmFjRlJVUm5kV|VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVW)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 6 time
  45. /(Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVUm1GalJsSlZVbTVrV|Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVU2pSV2JGcHlWMjVrVl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 7 time
  46. ];
  47. //limit max_iter
  48. max_iter = max_iter > regArr.length ? regArr.length : max_iter;
  49. //regex prefix - drag
  50. const regInvalid = /[^\w\+\/=]/;
  51.  
  52. //update chk
  53. var upd_chk = true;
  54.  
  55. //info param
  56. const sc_name = '['+GM.info.script.name+']';
  57. const sc_name_upd = '['+GM.info.script.name+'-UPD]';
  58. const sc_ver = GM.info.script.version;
  59.  
  60. //auto add padding - add '=' padding in base64 encoded string
  61. function base64AddPadding(str) {
  62. return str + Array((4 - str.length % 4) % 4 + 1).join('=');
  63. }
  64.  
  65. //base64 decode
  66. var Base64 = {
  67. _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  68. decode : function (input) {
  69. var output = "";
  70. var chr1, chr2, chr3;
  71. var enc1, enc2, enc3, enc4;
  72. var i = 0;
  73.  
  74. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  75.  
  76. while (i < input.length) {
  77. enc1 = this._keyStr.indexOf(input.charAt(i++));
  78. enc2 = this._keyStr.indexOf(input.charAt(i++));
  79. enc3 = this._keyStr.indexOf(input.charAt(i++));
  80. enc4 = this._keyStr.indexOf(input.charAt(i++));
  81.  
  82. chr1 = (enc1 << 2) | (enc2 >> 4);
  83. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  84. chr3 = ((enc3 & 3) << 6) | enc4;
  85.  
  86. //last bits
  87. output = output + String.fromCharCode(chr1);
  88. if (enc3 != 64) { //=
  89. output = output + String.fromCharCode(chr2);
  90. }
  91. if (enc4 != 64) { //==
  92. output = output + String.fromCharCode(chr3);
  93. }
  94. }
  95.  
  96. output = Base64._utf8_decode(output);
  97. return output;
  98. },
  99. // private method for UTF-8 decoding
  100. _utf8_decode : function (utftext) {
  101. var string = "";
  102. var i = 0;
  103. var c = 0;
  104. var c1 = 0;
  105. var c2 = 0;
  106. var c3 = 0;
  107.  
  108. while ( i < utftext.length ) {
  109. c = utftext.charCodeAt(i);
  110. if (c < 128) {
  111. string += String.fromCharCode(c);
  112. i++;
  113. }
  114. else if((c > 191) && (c < 224)) {
  115. c2 = utftext.charCodeAt(i+1);
  116. string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  117. i += 2;
  118. }
  119. else {
  120. c2 = utftext.charCodeAt(i+1);
  121. c3 = utftext.charCodeAt(i+2);
  122. string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  123. i += 3;
  124. }
  125. }
  126. return string;
  127. }
  128. };
  129.  
  130. var hindex = 0; //total decode count
  131.  
  132. //drag function comparison
  133. var lastSelected = document;
  134. var lastSelectedTime = Date.now();
  135.  
  136. //create link each components
  137. function createLink(src, index, url, depth) {
  138. //n번째 링크 (base64 깊이: 0) [ ABCDEF= ]
  139. return '<a href="' + url + '" title="' + url + ' (새 창으로 열기)" target="_blank" rel="external nofollow noopener noreferrer">' + index.toString() + '번째 링크 (base64 깊이: ' + depth.toString() + ')</a> [ <span style="font-size: 87.5%;color: rgb(71 188 115);">' + src.toString() + '</span> ]';
  140. }
  141.  
  142. //decode & generate
  143. function replacerGen(numIter) {
  144. return function(source) {
  145. try {
  146. rstring = ""; //return msg
  147. console.log('\n'+sc_name,'No.',(hindex+1).toString(),'encoded link:\n', source.toString()); //source
  148.  
  149. //decode
  150. var converted = Base64.decode(base64AddPadding(source));
  151. //attempt to decode nested base64 encoded string
  152. for(var i=0; i<numIter; i++) {
  153. converted = Base64.decode(base64AddPadding(converted));
  154. }
  155. hindex++;
  156.  
  157. //remove invalid string - �
  158. converted = decodeURI(encodeURI(converted).replaceAll('%00', ''));
  159. console.log(sc_name,'No.',hindex.toString(),'decode completed:\n',converted.toString()); //converted
  160.  
  161. //split by new line
  162. converted = converted.split(/\r?\n/);
  163. //single component
  164. if (converted.length == 2 && converted[converted.length-1] == '') {
  165. rstring += createLink(source, hindex, converted[0], numIter+1);
  166. //multiple component
  167. } else if (converted.length > 1) {
  168. rstring += '[ <span style="font-size: 87.5%;color: rgb(71 188 115);">' + source.toString() + '</span> ]';
  169. nindex = 1;
  170. converted.forEach(function(j) {
  171. if (j != '') {
  172. rstring += '<br>' + createLink('링크 자동 분할 : '+nindex.toString()+'번째', hindex, j, numIter+1);
  173. hindex++;
  174. nindex++;
  175. }
  176. });
  177. //apply last components
  178. hindex--;
  179. nindex--;
  180. console.log(sc_name,'No.',hindex.toString(),'- splitted total :', nindex.toString());
  181. rstring = '<span style="color: rgb(232 62 140);"><b><i>분할된 링크 총 '+nindex.toString()+'개</i></b></span> ' + rstring;
  182. } else rstring += createLink(source, hindex, converted, numIter+1);
  183. return rstring;
  184. } catch(e) {
  185. console.warn('\n'+sc_name,'error occured during decoding:', e);
  186. console.warn(sc_name,'base64 decode fail:', source.toString());
  187. }
  188. return '<span style="color: rgb(255 0 0);">[ base64 변환 실패: '+source.toString()+' ]</span>';
  189. };
  190. }
  191.  
  192. //user drag event
  193. //function disabled
  194. function selClicked(event) {
  195. var sel = document.getSelection().toString();
  196. if (!sel.match(regInvalid) && sel.length >= 10 && lastSelectedTime + 200 < Date.now()) {
  197. try {
  198. console.log(sc_name,'live match - ' + sel.toString());
  199. var converted = decodeURI(encodeURI(Base64.decode(base64AddPadding(sel))).replaceAll('%00', ''));
  200. console.log(sc_name,'converted - ' + converted.toString());
  201. this.innerHTML = this.innerHTML.replace(sel, converted);
  202. } catch (e) {
  203. return;
  204. } finally {
  205. this.removeEventListener('click', selClicked);
  206. }
  207. }
  208. }
  209.  
  210. //update check
  211. function checkForUpdate(){
  212. if (!upd_chk) {
  213. console.log(sc_name_upd,'updchk skipped.');
  214. return;
  215. }
  216. const tar_sc = 'https://update.greasyfork.org/scripts/482577/Arca%20base64%20autodecoder.user.js';
  217. fetch(tar_sc)
  218. .then(response => response.text())
  219. .then(data => {
  220. //extract version from greaskyfork script
  221. const match = data.match(/@version\s+(\d+\.\d+)/);
  222. if (match) {
  223. const tar_version = parseFloat(match[1]);
  224. const cur_version = parseFloat(sc_ver);
  225. //new version detected
  226. if (tar_version > cur_version) {
  227. console.log(sc_name_upd,'new version available. ('+cur_version+' -> '+tar_version+')');
  228. //y/n dialog
  229. if (window.confirm(sc_name+'\n새로운 버전이 감지되었습니다. 업데이트를 권장합니다.\n( 기존버전 : '+cur_version+', 새로운 버전 : '+tar_version+' )\n\n취소를 누르면 앞으로 업데이트 확인 알림을 띄우지 않습니다.')) {
  230. console.log(sc_name_upd,'opening source url..');
  231. window.location.replace(tar_sc);
  232. } else {
  233. GM.setValue('chkupd', false);
  234. console.log(sc_name_upd,'updchk disabled.');
  235. window.alert(sc_name+'\n앞으로 업데이트 확인 알림을 띄우지 않습니다.');
  236. }
  237. } else {
  238. console.log(sc_name_upd,'latest version detected.');
  239. }
  240. } else {
  241. console.error(sc_name_upd,'unable to extract version..');
  242. }
  243. })
  244. .catch(error => {
  245. upd_chk = false;
  246. console.error(sc_name_upd,'link unreachable.. -', error);
  247. });
  248. upd_chk = false;
  249. }
  250.  
  251. //main
  252. (async () => {
  253. 'use strict';
  254.  
  255. //chk browser env
  256. if(((navigator.language || navigator.userLanguage) != 'ko-KR')) console.warn('Warning! this script support only korean language..');
  257.  
  258. console.log(sc_name,'V',sc_ver,'ready');
  259.  
  260. //get extension env
  261. if(GM.info.scriptWillUpdate) {
  262. //get env
  263. upd_chk = await GM.getValue('chkupd', true);
  264.  
  265. //chk update
  266. await checkForUpdate();
  267. }
  268.  
  269. //article
  270. var article = document.getElementsByClassName("article-content")[0];
  271. for(var i=0; i<max_iter; i++) {
  272. article.innerHTML = article.innerHTML.replaceAll(regArr[i], replacerGen(i));
  273. }
  274.  
  275. //comment
  276. var comments = document.getElementsByClassName("list-area");
  277. if(comments.length != 0) {
  278. for(var j=0; j<max_iter; j++) {
  279. comments[0].innerHTML = comments[0].innerHTML.replaceAll(regArr[j], replacerGen(j));
  280. }
  281. }
  282.  
  283. /*
  284. //user drag
  285. console.log('USR-Drag enabled.');
  286. document.addEventListener('selectionchange', function() {
  287. var sel = document.getSelection().anchorNode;
  288. if(sel) {
  289. sel = sel.parentElement;
  290. if(sel != lastSelected) {
  291. lastSelected.removeEventListener('click', selClicked);
  292. sel.addEventListener('click', selClicked);
  293. lastSelected = sel;
  294. lastSelectedTime = Date.now();
  295. }
  296. }
  297. })
  298. */
  299.  
  300. console.log(sc_name,'total',hindex.toString(),'decode task completed.');
  301. })();