Arca base64 autodecoder

Arca.live Base64 auto decoder

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            Arca base64 autodecoder
// @name:ko         아카라이브 Base64 자동 디코더
// @version         1.201
// @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.getValue
// @grant           GM.setValue
// @grant           GM.registerMenuCommand
// @grant           GM.unregisterMenuCommand
// ==/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
 * 1.20 - add menu(base64 depth, user-drag auto decoding, hide encoded link, update notify)
 * 1.201 - base64 depth extends - 11, temporary disable - drag auto decoding
*/

//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
    /(Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVbTFHYWxKc1NsWlZiVFZyV|Vm0xd1IxbFdXWGxUV0doWFYwZFNUMVpzWkc5V2JHeFZVMnBTVjJKR2NIbFdNalZyVm)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 8 time
    /(Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZiVEZIWVd4S2MxTnNXbFppVkZaeV|Vm0weGQxSXhiRmRYV0d4VVYwZG9XRll3WkZOVU1WcHpXa2M1VjJKSGVGWlZNbkJUVmpKS1IyTkliRmROYWxaeVZt)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 9 time
    /(Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFppVkVaSVdWZDRTMk14VG5OWGJGcHBWa1phZ|Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZad)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 10 time
    /(Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcHBWa1ZhU1ZkV1pEUlRNazE0Vkc1T1dHSkdjSEJXYTFwaF|Vm0wd2QyVkhVWGhUV0docFVtMVNXVll3WkRSV1ZsbDNXa2M1V0ZKc2JETlhhMXBQVmxVeFYyTkljRmhoTWsweFZtcEtTMU5IVmtkWGJGcE9ZbXRLVlZadGNFdFRNVWw1Vkd0c2FWSnRVazlaVjNoaFpWWmFk)(\w|=|\+|\/)*(?=[^\+=\w\/])/g, //encoding 11 time
];

//regex prefix - drag
const regInvalid = /[^\w\+\/=]/;

//update chk
let updateAvailble = true;

let draggableActivated = false;

//encoded link list, [uuid]: [encoded link]
let encodedList = {};

//total decode count
let hindex = 0;

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

//logging prefix, param
const sc_name = '['+GM.info.script.name+']';
const sc_name_upd = '['+GM.info.script.name+'-UPD]';
const sc_name_par = '['+GM.info.script.name+'-PAR]';

let localParameter = {
  'basedepth': {
    'param_name': 'basedepth',
    'name': 'base64 깊이 조절하기 - 현재 값 : 알수없음',
    'desc': '자동 base64 디코딩 깊이를 조절할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_depth,
    'value': 3,
    'visible': true,
    'def_value': 3,
  },
  'enclinkhide': {
    'param_name': 'enclinkhide',
    'name': '인코딩된 링크 보이기',
    'desc': '자동 base64 디코딩 전 인코딩된 링크 표시 여부를 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_enchide,
    'value': false,
    'visible': true,
    'def_value': true,
  },
  'draggable': {
    'param_name': 'draggable',
    'name': '드래그 시 자동 디코딩 켜기',
    'desc': '드래그 시 자동으로 base64로 디코딩할지 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_drag,
    'value': false,
    'visible': false,
    'def_value': false,
  },
  'updatechk': {
    'param_name': 'chkupd',
    'name': '업데이트 알림 끄기',
    'desc': '새 버전이 나올 시 업데이트 확인 알림을 띄울지 여부를 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_updchk,
    'value': true,
    'visible': true,
    'def_value': true,
  },
};

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

//base64 decode
const Base64 = {
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  decode : function (input) {
    let output = "";
    let chr1, chr2, chr3;
    let enc1, enc2, enc3, enc4;
    let 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) {
    let string = "";
    let i = 0;
    let c = 0;
    let c1 = 0;
    let c2 = 0;
    let 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;
  }
};

//encoded link click callback
function showEncodedLink(id) {
  //check already clicked
  if (encodedList.hasOwnProperty(id)) {
    console.log(sc_name,'show encoded link -',encodedList[id]);
    const self = document.getElementById(id);
    self.innerHTML = encodedList[id];
    self.style.color = 'rgb(71 88 188)';
    delete encodedList[id];
  }
  return;
}

//link area
function createLinkArea(src) {
  return '<span style="font-size: 87.5%;color: rgb(71 188 115);">[ ' + src.toString() + ' ]</span>';
}

//encoded link element
function createEncElem(src) {
  const uuid = 'abad_'+self.crypto.randomUUID();
  encodedList[uuid] = src;
  return '<span id="' + uuid.toString() + '">' + '클릭 시 인코딩된 코드 보기' + '</span>';
}

//link creation
function createLink(src, index, url, depth, hidelink = false) {
  //n번째 링크 (base64 깊이: 0) [ ABCDEF= / 클릭시 원본~ ]
  return '<a href="'+url+'" title="'+url+' (새 창으로 열기)" target="_blank" rel="external nofollow noopener noreferrer">'+index.toString()+'번째 링크 (base64 깊이: '+depth.toString()+')</a> '+(hidelink?createLinkArea(createEncElem(src)):createLinkArea(src))+'';
}

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

      //decode
      let converted = Base64.decode(base64AddPadding(source));
      //attempt to decode nested base64 encoded string
      for(let 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,'decode completed (depth:',numIter+1,'):\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, !localParameter.enclinkhide.value);
      //multiple component
      } else if (converted.length > 1) {
        rstring += createLinkArea(localParameter.enclinkhide.value?source.toString():createEncElem(source.toString()));

        let nindex = 1;
        converted.forEach(function(i) {
          if (i != '') {
            rstring += '<br>' + createLink('<span style="color: rgb(71 188 115);">링크 자동 분할 : '+nindex.toString()+'번째</span>', hindex, i, numIter+1);
            hindex++;
            nindex++;
          }
        });
        //apply last components
        hindex--;
        nindex--;

        console.log(sc_name,'No.',hindex,'- splitted total :', nindex);
        rstring = '<span style="color: rgb(232 62 140);"><b><i>분할된 링크 총 '+nindex.toString()+'개</i></b></span> ' + rstring;
      } else rstring += createLink(source, hindex, converted, numIter+1, !localParameter.enclinkhide.value);
      return rstring;
    } catch(e) {
      console.warn('\n'+sc_name,'error occured during decoding:', e);
      console.warn(sc_name,'base64 decode fail:', source);
    }
    return '<span style="color: rgb(255 0 0);">[ base64 변환 실패: '+source.toString()+' ]</span>';
  };
}

//user drag event
function selClicked(event) {
  const sel = document.getSelection().toString();
  if (!sel.match(regInvalid) && sel.length >= 10 && lastSelectedTime + 200 < Date.now()) {
    try {
      console.log(sc_name,'live match -',sel.toString());
      let 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);
    }
  }
}

//user drag activate
function activateDragDecoding() {
  if(draggableActivated) {
    console.log(sc_name,'USR-Drag already enabled.');
    return;
  }
  draggableActivated = true;
  console.log(sc_name,'USR-Drag enabled.');
  document.addEventListener('selectionchange', function() {
    let 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();
      }
    }
  });
}

//update check
function checkForUpdate(){
  if (!updateAvailble || !localParameter.updatechk.value) {
    console.log(sc_name_upd,'updchk skipped.');
    return;
  }
  console.log(sc_name_upd,'checking for update...');
  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(GM.info.script.version);
      //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\n취소를 누르면 앞으로 업데이트 알림을 띄우지 않습니다.')) {
          //get extension env
          if(!GM.info.scriptWillUpdate) {
            console.log(sc_name_upd,'extension not allowed auto update..');
            if (window.confirm(sc_name+'\n주의! 스크립트 내용 변경 등으로 인해 확장프로그램 내 자동 업데이트가 꺼져있는 것 같습니다.\n업데이트 시 기존 스크립트에 덮어쓰게 되어 기존 내용이 손실될 수 있습니다.\n이 점 확인 후 업데이트 바랍니다.\n\n(계속하려면 확인, 취소하려면 취소를 눌러주세요.)')) {
              console.log(sc_name_upd,'opening source url..');
              window.location.replace(tar_sc);
            } else {
              console.log(sc_name_upd,"user canceled.");
            }
          } else {
            console.log(sc_name_upd,'opening source url..');
            window.location.replace(tar_sc);
          }
        } else {
          console.log(sc_name_par,'updatechk change',true.toString(),'to',false.toString());
          localParameter.updatechk.value = false;
          try {
            GM.setValue('chkupd', false);
            console.log(sc_name_par,"updatechk change successful");
            window.alert(sc_name+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
            menucmd_update();
          } catch(e) {
            localParameter.updatechk.value = true;
            console.error(sc_name_par,"updatechk change fail -", e);
            window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
          }
        }
      } else {
        console.log(sc_name_upd,'latest version', cur_version, 'detected. (eth:',tar_version,')');
      }
    } else {
      console.error(sc_name_upd,'unable to extract version..');
    }
  })
  .catch(error => {
    updateAvailble = false;
    console.error(sc_name_upd,'link unreachable.. -', error);
  });
  updateAvailble = false;
}

function menucmd_update(fist_run = false) {
  //pre process
  localParameter.basedepth.value = localParameter.basedepth.value > regArr.length ? regArr.length : localParameter.basedepth.value;

  //update menu name
  localParameter.basedepth.name = 'base64 깊이 조절하기 - 현재 값 : '+localParameter.basedepth.value+'회';
  localParameter.enclinkhide.name = '인코딩된 링크 '+(localParameter.enclinkhide.value?'숨기기':'보이기');
  localParameter.draggable.name = '드래그 시 자동 디코딩 '+(localParameter.draggable.value?'끄기':'켜기');
  localParameter.updatechk.name = '업데이트 알림 '+(localParameter.updatechk.value?'끄기':'켜기');

  //remove exist menu cmd
  if (!fist_run) {
    Object.keys(localParameter).forEach(function(i){
      try {
        GM.unregisterMenuCommand(localParameter[i].id);
      } catch(_) {}
    });
  }
  //monkey menu cmd register
  try {
    Object.keys(localParameter).forEach(function(i){
      if (localParameter[i].visible) {
        localParameter[i].id = GM.registerMenuCommand(localParameter[i].name, localParameter[i].func, {title:localParameter[i].desc});
      } else {
        localParameter[i].value = localParameter[i].def_value;
      }
    });
    console.log(sc_name_par,'ext opt pannel',(fist_run?'registered':'reloaded'));
  } catch(e) {
    console.error(sc_name_par,'err - ext opt pannel',(fist_run?'register':'reload'),'- ', e);
    Object.keys(localParameter).forEach(function(i){
      try {
        GM.unregisterMenuCommand(localParameter[i].id);
      } catch(_) {}
    });
  }
}

function menucmd_f_depth() {
  menucmd_update();
  const prev_value = localParameter.basedepth.value;
  const str_common_1 = ' ( 지정 가능한 범위: 1~'+regArr.length.toString()+' )';
  while(true) {
    const input = window.prompt(sc_name+'\nBase64 자동 디코딩 중첩 횟수를 얼마로 지정할까요?\n(인코딩을 인코딩한 것을 여러번 반복한걸 자동으로 풀어냅니다.)\n현재 값: '+prev_value.toString()+'회,'+(prev_value == 3 ? '' : ' 기본값: 3회,')+str_common_1+'\n\n(값을 너무 크게 지정하면 컴퓨터 성능에 영향을 줄 수 있습니다.)', prev_value);
    if (input == null) {
      console.log(sc_name,'basedepth change canceled.');
      break;
    }
    if(!isNaN(input)){
      const tar_value = parseInt(input);
      if(tar_value == prev_value) {
        window.alert(sc_name+'\n동일한 값을 입력했습니다, 현재 값: '+prev_value+'회');
      } else if(tar_value >= 1 && tar_value <= regArr.length) {
        console.log(sc_name_par,'basedepth change',prev_value.toString(),'to',tar_value.toString());
        localParameter.basedepth.value = tar_value;
        try {
          GM.setValue('basedepth', tar_value);
          menucmd_update();
          console.log(sc_name_par,"basedepth change successful");
          window.alert(sc_name+'\n값이 '+prev_value.toString()+'에서 '+tar_value.toString()+'으로 변경이 완료되었습니다.\n\n(사이트를 새로고침해야 반영됩니다.)');
        } catch(e) {
          localParameter.basedepth.value = prev_value;
          console.error(sc_name_par,"basedepth change fail -", e);
          window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
        }
        break;
      } else {
        window.alert(sc_name+'\n'+tar_value+'(으)로 설정할 수 없습니다.\n범위를 초과하였습니다..'+str_common_1);
      }
    } else {
      window.alert(sc_name+'\n'+input+'은(는)숫자가 아닙니다.\n숫자만 입력해주세요..'+str_common_1);
    }
  }
  menucmd_update();
}

function menucmd_f_enchide() {
  menucmd_update();
  const curr_state = localParameter.enclinkhide.value;
  if(window.confirm(sc_name+'\n디코딩 시 인코딩된 링크를 '+(curr_state?'숨기시':'표시하')+'겠습니까?\n\n(앞으로 디코딩 전 인코딩된 링크를\n"'+(curr_state?'클릭 시 기존링크 보기':'aHR0cHM6Ly9hcmNhLmx..')+'"와 같은 형태로 보여줍니다.)')) {
    const set_state = !curr_state;
    console.log(sc_name_par,'enchide change',curr_state.toString(),'to',set_state.toString());
    localParameter.enclinkhide.value = set_state;
    try {
      GM.setValue('enclinkhide', set_state);
      menucmd_update();
      console.log(sc_name_par,"updatechk change successful");
      if(set_state) {
        window.alert(sc_name+'\n앞으로 인코딩된 링크를 표시합니다.\n\n(새로고침해야 적용됩니다.)');
      } else {
        window.alert(sc_name+'\n앞으로 인코딩된 링크를 숨깁니다.');
      }
    } catch(e) {
      localParameter.enclinkhide.value = curr_state;
      console.error(sc_name_par,"enchide change fail -", e);
      window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    }
  } else {
    console.log(sc_name,'enchide change canceled.');
  }
  menucmd_update();
}

function menucmd_f_drag() {
  menucmd_update();
  const curr_state = localParameter.draggable.value;
  if(window.confirm(sc_name+'\n드래그 시 자동 디코딩을 '+(curr_state?'비':'')+'활성화 하시겠습니까?\n\n(앞으로 인코딩된 부분을 드래그'+(curr_state?'해도 자동으로 디코딩되지 않습':' 시 Base64로 인코딩된것으로\n판단 되면 자동으로 디코딩을 시도합')+'"니다.)')) {
    const set_state = !curr_state;
    console.log(sc_name_par,'draggable change',curr_state.toString(),'to',set_state.toString());
    localParameter.draggable.value = set_state;
    try {
      GM.setValue('draggable', set_state);
      menucmd_update();
      console.log(sc_name_par,"draggable change successful");
      if(set_state) {
        try {
          activateDragDecoding();
          window.alert(sc_name+'\n앞으로 드래그 시 자동 디코딩을 진행합니다.');
        } catch(e) {
          console.error(sc_name,"draggable activate fail -", e);
          window.alert(sc_name+'\n드래그 시 자동 디코딩 활성화 중 문제가 발생했습니다.\n새로고침이 필요합니다..');
        }
      } else {
        window.alert(sc_name+'\n앞으로 드래그 해도 반응하지 않습니다.\n\n(새로고침해야 적용됩니다.)');
      }
    } catch(e) {
      localParameter.draggable.value = curr_state;
      console.error(sc_name_par,"draggable change fail -", e);
      window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    }
  } else {
    console.log(sc_name,'draggable change canceled.');
  }
  menucmd_update();
}

function menucmd_f_updchk() {
  menucmd_update();
  const curr_state = localParameter.updatechk.value;
  if(window.confirm(sc_name+'\n업데이트 알림을 '+(curr_state?'끄':'켜')+'시겠습니까?\n\n(앞으로 업데이트가 있'+(curr_state?'어도 알려주지 않습':'으면 자동으로 알려줍')+'니다.)')) {
    const set_state = !curr_state;
    console.log(sc_name_par,'updatechk change',curr_state.toString(),'to',set_state.toString());
    localParameter.updatechk.value = set_state;
    try {
      GM.setValue('chkupd', set_state);
      console.log(sc_name_par,"updatechk change successful");
      if(set_state) {
        window.alert(sc_name+'\n앞으로 업데이트가 존재하면 알림을 띄웁니다.');
        checkForUpdate();
      } else {
        window.alert(sc_name+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
      }
    } catch(e) {
      localParameter.updatechk.value = curr_state;
      console.error(sc_name_par,"updatechk change fail -", e);
      window.alert(sc_name+'\n파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    }
  } else {
    console.log(sc_name,'updatechk change canceled.');
  }
  menucmd_update();
}

//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',GM.info.script.version,'enabled');

  //load parameter
  try {
    for (const i of Object.keys(localParameter)) {
      localParameter[i].value = await GM.getValue(localParameter[i].param_name, localParameter[i].value);
    }
    console.log(sc_name_par, 'parameter load successful.');
  } catch(e) {
    console.error(sc_name_par,'err - get sc parameter - ', e);
  }

  //apply parameter and register monkey menu command
  menucmd_update(true);

  //chk update
  await checkForUpdate();

  //drag auto decoding
  if (localParameter.draggable.value) {
    activateDragDecoding();
  }

  console.log(sc_name,'ready');
  //main procedure

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

  //comment
  let comments = document.getElementsByClassName("list-area");
  if (comments.length != 0) {
    for(let i=0; i<localParameter.basedepth.value; i++) {
      comments[0].innerHTML = comments[0].innerHTML.replaceAll(regArr[i], replacerGen(i));
    }
  }
  console.log(sc_name,'total',hindex,'decode task completed.');

  //add eventlistner - click, show original encoded link
  if (!localParameter.enclinkhide.value) {
    Object.keys(encodedList).forEach(function(i) {
      document.getElementById(i).addEventListener('click',  () => {showEncodedLink(i);});
    });
  }
})();