Arca base64 autodecoder

Arca.live Base64 auto decoder

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Arca base64 autodecoder
// @name:ko         아카라이브 Base64 자동 디코더
// @version         1.18
// @author          Laria
// @match           https://arca.live/b/*/*
// @description     Arca.live Base64 auto decoder
// @description:ko  아카라이브 Base64 자동 복호화 스크립트
// @icon            https://www.google.com/s2/favicons?sz=64&domain=arca.live
// @license         MIT
// @encoding        utf-8
// @run-at          document-end
// @supportURL      https://greasyfork.org/ko/scripts/482577-arca-base64-autodecoder
// @namespace       https://greasyfork.org/users/1235854
// @grant           GM.setValue
// @grant           GM.getValue
// ==/UserScript==

/*
 * == Change log ==
 * 1.0 - Release
 * 1.1 - Invalid character update (replace -> replaceAll)
 * 1.11 - Improved show multiple links
 * 1.12 - Show Single links Bugfix
 * 1.13 - Bugfix 1.12
 * 1.14 - Base64 add padding func
 * 1.15 - Add annotation, display improvements
 * 1.16 - Display improved - CSS applied
 * 1.17 - var safe, max_iter defined (~7, def:3)
 * 1.18 - auto update check, log system
*/

//max attempt decode depth, defalut depth : 3
//Caution! browser performance impact..
var max_iter = 3;

//base64 encoded(http:/*, https:/*) string prefix
const regArr = [
    /(aHR0cDovL|aHR0cHM6Ly)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 1 time
    /(YUhSMGNEb3ZM|YUhSMGNITTZMe)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 2 time
    /(WVVoU01HTkViM1pN|WVVoU01HTklUVFpNZ)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 3 time
    /(V1ZWb1UwMUhUa1ZpTTFwT|V1ZWb1UwMUhUa2xVVkZwTl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 4 time
    /(VjFaV2IxVXdNVWhVYTFacFRURndU|VjFaV2IxVXdNVWhVYTJ4VlZrWndUb)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 5 time
    /(VmpGYVYySXhWWGROVldoVllURmFjRlJVUm5kV|VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVW)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 6 time
    /(Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVUm1GalJsSlZVbTVrV|Vm1wR1lWWXlTWGhXV0dST1ZsZG9WbGxVU2pSV2JGcHlWMjVrVl)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 7 time
];
//limit max_iter
max_iter = max_iter > regArr.length ? regArr.length : max_iter;
//regex prefix - drag
const regInvalid = /[^\w\+\/=]/;

//update chk
var upd_chk = true;

//info param
const sc_name = '['+GM.info.script.name+']';
const sc_name_upd = '['+GM.info.script.name+'-UPD]';
const sc_ver = GM.info.script.version;

//auto add padding - add '=' padding in base64 encoded string
function base64AddPadding(str) {
    return str + Array((4 - str.length % 4) % 4 + 1).join('=');
}

//base64 decode
var Base64 = {
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  decode : function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {
      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      //last bits
      output = output + String.fromCharCode(chr1);
      if (enc3 != 64) { //=
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) { //==
        output = output + String.fromCharCode(chr3);
      }
    }

    output = Base64._utf8_decode(output);
    return output;
  },
  // private method for UTF-8 decoding
  _utf8_decode : function (utftext) {
    var string = "";
    var i = 0;
    var c = 0;
    var c1 = 0;
    var c2 = 0;
    var c3 = 0;

    while ( i < utftext.length ) {
      c = utftext.charCodeAt(i);
      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      }
      else if((c > 191) && (c < 224)) {
        c2 = utftext.charCodeAt(i+1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      }
      else {
        c2 = utftext.charCodeAt(i+1);
        c3 = utftext.charCodeAt(i+2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }
    return string;
  }
};

var hindex = 0; //total decode count

//drag function comparison
var lastSelected = document;
var lastSelectedTime = Date.now();

//create link each components
function createLink(src, index, url, depth) {
  //n번째 링크 (base64 깊이: 0) [ ABCDEF= ]
  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> ]';
}

//decode & generate
function replacerGen(numIter) {
  return function(source) {
    try {
      rstring = ""; //return msg
      console.log('\n'+sc_name,'No.',(hindex+1).toString(),'encoded link:\n', source.toString()); //source

      //decode
      var converted = Base64.decode(base64AddPadding(source));
      //attempt to decode nested base64 encoded string
      for(var i=0; i<numIter; i++) {
          converted = Base64.decode(base64AddPadding(converted));
      }
      hindex++;

      //remove invalid string - �
      converted = decodeURI(encodeURI(converted).replaceAll('%00', ''));
      console.log(sc_name,'No.',hindex.toString(),'decode completed:\n',converted.toString()); //converted

      //split by new line
      converted = converted.split(/\r?\n/);
      //single component
      if (converted.length == 2 && converted[converted.length-1] == '') {
        rstring += createLink(source, hindex, converted[0], numIter+1);
      //multiple component
      } else if (converted.length > 1) {
        rstring += '[ <span style="font-size: 87.5%;color: rgb(71 188 115);">' + source.toString() + '</span> ]';
        nindex = 1;
        converted.forEach(function(j) {
          if (j != '') {
            rstring += '<br>' + createLink('링크 자동 분할 : '+nindex.toString()+'번째', hindex, j, numIter+1);
            hindex++;
            nindex++;
          }
        });
        //apply last components
        hindex--;
        nindex--;
        console.log(sc_name,'No.',hindex.toString(),'- splitted total :', nindex.toString());
        rstring = '<span style="color: rgb(232 62 140);"><b><i>분할된 링크 총 '+nindex.toString()+'개</i></b></span> ' + rstring;
      } else rstring += createLink(source, hindex, converted, numIter+1);
      return rstring;
    } catch(e) {
      console.warn('\n'+sc_name,'error occured during decoding:', e);
      console.warn(sc_name,'base64 decode fail:', source.toString());
    }
    return '<span style="color: rgb(255 0 0);">[ base64 변환 실패: '+source.toString()+' ]</span>';
  };
}

//user drag event
//function disabled
function selClicked(event) {
  var sel = document.getSelection().toString();
  if (!sel.match(regInvalid) && sel.length >= 10 && lastSelectedTime + 200 < Date.now()) {
    try {
      console.log(sc_name,'live match - ' + sel.toString());
      var converted = decodeURI(encodeURI(Base64.decode(base64AddPadding(sel))).replaceAll('%00', ''));
      console.log(sc_name,'converted - ' + converted.toString());
      this.innerHTML = this.innerHTML.replace(sel, converted);
    } catch (e) {
      return;
    } finally {
      this.removeEventListener('click', selClicked);
    }
  }
}

//update check
function checkForUpdate(){
  if (!upd_chk) {
    console.log(sc_name_upd,'updchk skipped.');
    return;
  }
  const tar_sc = 'https://update.greasyfork.org/scripts/482577/Arca%20base64%20autodecoder.user.js';
  fetch(tar_sc)
  .then(response => response.text())
  .then(data => {
    //extract version from greaskyfork script
    const match = data.match(/@version\s+(\d+\.\d+)/);
    if (match) {
      const tar_version = parseFloat(match[1]);
      const cur_version = parseFloat(sc_ver);
      //new version detected
      if (tar_version > cur_version) {
        console.log(sc_name_upd,'new version available. ('+cur_version+' -> '+tar_version+')');
        //y/n dialog
        if (window.confirm(sc_name+'\n새로운 버전이 감지되었습니다. 업데이트를 권장합니다.\n( 기존버전 : '+cur_version+', 새로운 버전 : '+tar_version+' )\n\n취소를 누르면 앞으로 업데이트 확인 알림을 띄우지 않습니다.')) {
          console.log(sc_name_upd,'opening source url..');
          window.location.replace(tar_sc);
        } else {
          GM.setValue('chkupd', false);
          console.log(sc_name_upd,'updchk disabled.');
          window.alert(sc_name+'\n앞으로 업데이트 확인 알림을 띄우지 않습니다.');
        }
      } else {
        console.log(sc_name_upd,'latest version detected.');
      }
    } else {
      console.error(sc_name_upd,'unable to extract version..');
    }
  })
  .catch(error => {
    upd_chk = false;
    console.error(sc_name_upd,'link unreachable.. -', error);
  });
  upd_chk = false;
}

//main
(async () => {
  'use strict';

  //chk browser env
  if(((navigator.language || navigator.userLanguage) != 'ko-KR')) console.warn('Warning! this script support only korean language..');

  console.log(sc_name,'V',sc_ver,'ready');

  //get extension env
  if(GM.info.scriptWillUpdate) {
    //get env
    upd_chk = await GM.getValue('chkupd', true);

    //chk update
    await checkForUpdate();
  }

  //article
  var article = document.getElementsByClassName("article-content")[0];
  for(var i=0; i<max_iter; i++) {
    article.innerHTML = article.innerHTML.replaceAll(regArr[i], replacerGen(i));
  }

  //comment
  var comments = document.getElementsByClassName("list-area");
  if(comments.length != 0) {
    for(var j=0; j<max_iter; j++) {
      comments[0].innerHTML = comments[0].innerHTML.replaceAll(regArr[j], replacerGen(j));
    }
  }

  /*
  //user drag
  console.log('USR-Drag enabled.');
  document.addEventListener('selectionchange', function() {
    var sel = document.getSelection().anchorNode;
    if(sel) {
      sel = sel.parentElement;
      if(sel != lastSelected) {
        lastSelected.removeEventListener('click', selClicked);
        sel.addEventListener('click', selClicked);
        lastSelected = sel;
        lastSelectedTime = Date.now();
      }
    }
  })
  */

  console.log(sc_name,'total',hindex.toString(),'decode task completed.');
})();