Arca base64 autodecoder

Arca.live Base64 auto decoder

目前為 2023-12-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name            Arca base64 autodecoder
// @name:ko         아카라이브 Base64 자동 디코더
// @version         1.203
// @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.deleteValue
// @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
 * 1.202 - improve encoded link click callback, feature block in edit mode, enable drag auto decoding
 * 1.203 - add menu(restore defaults)
*/

//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, fail->false
let updateAvailble = true;

//auto drag decoding enable status
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]';

//script menu parameter, user changeable
let localParameter = {
  'basedepth': {
    'param_name': 'basedepth',
    'name': 'base64 깊이 조절하기 - 현재 값 : 알수없음',
    'desc': '자동 base64 디코딩 깊이를 조절할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_depth,
    'value': 3,
    'def_value': 3,
    'visible': true,
  },
  'enclinkhide': {
    'param_name': 'enclinkhide',
    'name': '인코딩된 링크 [보이기/숨기기]',
    'desc': '자동 base64 디코딩 전 인코딩된 링크 표시 여부를 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_enchide,
    'value': false,
    'def_value': false,
    'visible': true,
  },
  'draggable': {
    'param_name': 'draggable',
    'name': '드래그 시 자동 디코딩 [켜기/끄기]',
    'desc': '드래그 시 자동으로 드래그한 부분을 base64로 디코딩할지 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_drag,
    'value': false,
    'def_value': false,
    'visible': true,
  },
  'updatechk': {
    'param_name': 'chkupd',
    'name': '업데이트 알림 [켜기/끄기]',
    'desc': '새 버전이 나올 시 업데이트 확인 알림을 띄울지 여부를 설정할 수 있습니다.',
    'id': -1,
    'func': menucmd_f_updchk,
    'value': true,
    'def_value': true,
    'visible': true,
  },
  'resetdefaults': {
    'param_name': 'none',
    'name': '스크립트 기본값 초기화',
    'desc': '스크립트의 사용자 설정을 초기화하고 설치 상태로 되돌립니다.',
    'id': -1,
    'func': menucmd_f_reset_defaults,
    'value': -1,
    'def_value': -1,
    'visible': 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(event) {
  const self = event.currentTarget;
  //check already clicked
  if (encodedList.hasOwnProperty(self.id)) {
    console.log(sc_name,'show encoded link -',encodedList[self.id]);
    self.innerHTML = encodedList[self.id];
    self.style.color = 'rgb(71 88 188)';
    self.title = '디코딩 전 인코딩된 링크입니다.';
    //delete encodedList[self.id];
  }
  return;
}

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

//encoded link element
function createMaskEncodedLink(src) {
  const uuid = 'abad_'+self.crypto.randomUUID();
  encodedList[uuid] = src;
  return '<span id="' + uuid.toString() + '" title="클릭 시 디코딩 전 인코딩된 링크를 표시합니다.">' + '클릭 시 인코딩된 코드 보기' + '</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?createEncodedLink(createMaskEncodedLink(src)):createEncodedLink(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 += createEncodedLink(localParameter.enclinkhide.value?source.toString():createMaskEncodedLink(source.toString()));

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

        console.log(sc_name,'No.',hindex_org,'- 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.meta.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);
              window.alert(sc_name+'\n업데이트 후 새로고침해야 적용됩니다.');
            } else {
              console.log(sc_name_upd,"user canceled.");
            }
          } else {
            console.log(sc_name_upd,'opening source url..');
            window.location.replace(tar_sc);
            window.alert(sc_name+'\n업데이트 후 새로고침해야 적용됩니다.');
          }
        } 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");
            menucmd_update();
            window.alert(sc_name+'\n앞으로 업데이트 알림을 띄우지 않습니다.');
          } 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);
          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파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
        } finally {
          menucmd_update();
        }
        break;
      } else {
        window.alert(sc_name+'\n'+tar_value+'(으)로 설정할 수 없습니다.\n범위를 초과하였습니다..'+str_common_1);
      }
    } else {
      window.alert(sc_name+'\n'+input+'은(는)숫자가 아닙니다.\n숫자만 입력해주세요..'+str_common_1);
    }
  }
}

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);
      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파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    } finally {
      menucmd_update();
    }
  } else {
    console.log(sc_name,'enchide change canceled.');
  }
}

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판단 되면 자동으로 디코딩을 시도합')+'니다.)\n\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);
      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파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    } finally {
      menucmd_update();
    }
  } else {
    console.log(sc_name,'draggable change canceled.');
  }
}

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앞으로 업데이트가 존재하면 알림을 띄웁니다.');
      } 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파라미터 변경 중 문제 발생, 로그를 확인해주세요..');
    } finally {
      menucmd_update();
    }
  } else {
    console.log(sc_name,'updatechk change canceled.');
  }
}

function menucmd_f_reset_defaults() {
  menucmd_update();
  if(window.confirm(sc_name+'\n정말 스크립트 설정을 기본값으로 초기화하시겠습니까?\n\n(초기화 완료 후 자동으로 새로고침됩니다.)')) {
    try {
      console.log(sc_name_par, 'remove all settings..');
      for (const i of Object.keys(localParameter)) {
        GM.deleteValue(localParameter[i].param_name);
      }
      console.log(sc_name_par, 'all parameter removed.');
      window.alert(sc_name+'\n설정값이 모두 제거되었습니다.\n\n(확인 후 현재 창이 자동으로 새로고침됩니다.)');
      window.location.reload(true);
    } catch(e) {
      console.error(sc_name_par,'err - get sc parameter - ', e);
      window.alert(sc_name+'\n경고! 파라미터 초기화 도중 문제가 발생했습니다. 로그를 참고해주세요..');
    }
  } else {
    console.log(sc_name,'settings restore canceled.');
  }
}

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

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

  let url_tail = window.location.href.match(/([/][a-z0-9_-]*[\/]?)$/g);
  if(url_tail != null) {
    if(url_tail[0] == '/edit') {
      console.log(sc_name,'edit mode detected, function disabled.');
      try {GM.registerMenuCommand("Edit 모드에서는 작동하지 않음", ()=>{window.alert(sc_name+'\n작성 또는 수정모드에서는 작동하지 않습니다..');});} catch(_) {}
      return;
    }
  }

  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].def_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];
  if (article != undefined) {
    for(let i=0; i<localParameter.basedepth.value; i++) {
      article.innerHTML = article.innerHTML.replaceAll(regArr[i], replacerGen(i));
    }
  } else console.warn(sc_name,'no article detected.');

  //comment
  let comments = document.getElementsByClassName("list-area");
  if (article != undefined) {
    if (comments.length != 0) {
      for(let i=0; i<localParameter.basedepth.value; i++) {
        comments[0].innerHTML = comments[0].innerHTML.replaceAll(regArr[i], replacerGen(i));
      }
    }
  } else console.warn(sc_name,'no comments detected.');
  console.log(sc_name,'total',hindex,'link 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, { once : true } );
    });
  }
})();