미디어위키:Gadget-PluginX.js: 두 판 사이의 차이

리버티게임(개발), 모두가 만들어가는 자유로운 게임
둘러보기로 이동 검색으로 이동
imported>이의섭
(hsl0님의 요청 수락)
잔글 (alert -> warning)
 
(사용자 4명의 중간 판 14개는 보이지 않습니다)
3번째 줄: 3번째 줄:
  * pluginX 시스템이 제대로 돌아가게 해 줍니다.
  * pluginX 시스템이 제대로 돌아가게 해 줍니다.
  * 작성자: [[사용자:Bd3076|Bd3076]]
  * 작성자: [[사용자:Bd3076|Bd3076]]
* v2.0.0 전체수정 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 7월 9일 (일) 23:20 (KST)
* v2.0.1 스크립트 해쉬값 검증 추가 --[[사용자:BANIP|BANIP]] 2023년 7월 10일 (월) 09:53 (KST)
* v2.0.2 자바스크립트가 위키텍스트로 파싱되는 문제 수정에 대한 스크립트쪽 소스 대응 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 9월 4일 (월) 10:40 (KST)
*/
*/
function pluginXCore() {
function pluginXCore() {
     var api = MediaWikiAPI();
     //pluginX를 문서에서 사용하지 않으면 종료
    var ipUser = false;
    if ($("#bodyContent .pluginx-script-wrapper").length == 0) return;
   
// 플러그인 목록 반환
    var getPluginList = function() {
var pluginItems = [];
/// 현재 문서 페이지에서 필요한 플러그인 수집
$("#bodyContent .pluginx-script-wrapper").each(function() {
var $this = $(this);
var url = $this.data('url');
// 이미 추가된 플러그인이면 추가하지 않음
if (pluginItems.find(function(item) { return item.url == url; })) return;
var script = $this.text();
// 플러그인 목록에 추가
pluginItems.push({
url: url,
name: $this.data('name'),
creator: $this.data('creator'),
doc: $this.data('doc'),
revid: $this.data('rev-id'),
script: script,
revTimestamp: $this.data('rev-timestamp'),
revUser: $this.data('rev-user'),
scriptHash:cyrb53(script),
});
});
return pluginItems;
};
if(mw.config.get("wgUserName") === null) ipUser = true;
var cyrb53 = function(str, seed){
if(seed === undefined) seed = 0;
var h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for(var i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1  = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2  = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
 
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
    var script_list = [];
 
    var pluginExist = false;
 
    var neededPluginExist = false;
var setting = {
    var html_before;
STORAGE_IDENTIFIER: "gadget-pluginx",
    var jsonDoc = "사용자:" + mw.config.get("wgUserName") + "/pluginX.json";
DEFAULT_CONFIG_TEXT:'{"trusted": [], "resetflag": ' + new Date().getTime() + ' }',
    var jsonData = JSON.parse(api.getDocument(jsonDoc));
get: function(){
// trusted: {doc:string, revid, revUser:string, revTimestamp:string, }[]
return JSON.parse(localStorage.getItem(setting.STORAGE_IDENTIFIER) || setting.DEFAULT_CONFIG_TEXT);
},
getTrustedItems: function(){
return setting.get().trusted;
},
getTrustedItemByDoc: function(doc){
return setting.getTrustedItems().find(function(item){ return item.doc == doc; });
},
set: function(data){
localStorage.setItem(setting.STORAGE_IDENTIFIER, JSON.stringify(data));
},
setTrustedItems: function(items){
var data = setting.get();
items.forEach(function(item){
var itemForPush = {
revid : item.revid,
revTimestamp : item.revTimestamp,
revUser : item.revUser,
doc : item.doc,
scriptHash : item.scriptHash,
};
var index = data.trusted.findIndex(function(trustedItem){ return trustedItem.doc == item.doc; });
if(index == -1){
data.trusted.push(itemForPush);
}else{
data.trusted[index] = itemForPush;
}
});
 
setting.set(data);
},
reset: function(){
localStorage.setItem(setting.STORAGE_IDENTIFIER,setting.DEFAULT_CONFIG_TEXT);
},
};
      
      
    /// 플러그인의 정보가 담긴 데이터를 만듭니다.
var installPopup = {
    var generateScriptData = function(_url, _name, _creator, _doc, _script, _revid){
// 플러그인 인스톨 페이지 가져오기
    return {
_getPopupElement:function(option){
    url: _url,
option = option || {};
    name: _name,
resolveCallback = option.resolveCallback || function(){};
    creator: _creator,
rejectCallback = option.rejectCallback || function(){};
    doc: _doc,
if(installPopup._$popupWrapper === undefined){
    script: _script,
// 설치 팝업 생성
    revid: _revid
var $popupWrapperOrigin = $(''
    };
+ '<div class="pluginx-popup-wrapper">'
    };
+ '<div class="pluginx-popup">'
   
+ '<div class="pluginx-popup-header">'
    /// 플러그인 목록을 만들어서 script_list[]에 담습니다.
+ '<div class="pluginx-popup-title">플러그인 설치가 필요합니다.</div>'
    var getPluginList = function(){
+ '</div>'
    $(".c_pluginX").each(function() {
+ '<div class="pluginx-popup-content-wrapper">'
    pluginExist = true;
+ '<div class="pluginx-popup-content">'
    var url = $(this).attr('data-url');
+ '<div class="pluginx-popup-code"></div>'
    var name = $(this).attr('data-name');
+ '<div class="pluginx-popup-summary"></div>'
    var creator = $(this).attr('data-creator');
+ '</div>'
    var doc = $(this).attr('data-doc');
+ '</div>'
    var revid = $(this).attr('data-revid');
+ '<div class="pluginx-popup-footer">'
   
+ '<div class="pluginx-trust-check-wrapper">'
    var script = api.getDocument(doc);
+ '<input type="checkbox" id="pluginx-trust-check" checked="checked" />'
   
+ '<label for="pluginx-trust-check" >다시 묻지 않기</label>'
    script_list.push(generateScriptData(url, name, creator, doc, script, revid));
+ '</div>'
   
+ '<div class="pluginx-btn-accept pluginx-close pluginx-btn">사용</div>'
    if(jsonData[doc] === undefined || jsonData[doc] !== revid){
+ '<div class="pluginx-btn-deny pluginx-close pluginx-btn">거부</div>'
    console.log(name);
+ '</div>'
    neededPluginExist = true;
+ '</div>'
    }
+ '</div>');
    });
 
    };
// 만든팝업 캐싱
   
installPopup._$popupWrapper = $popupWrapperOrigin;
    /// 플러그인을 실행합니다.
}
    var executePlugins = function(){
var $popupWrapper = installPopup._$popupWrapper.clone();
    if(html_before) document.getElementById('mw-content-text').innerHTML = html_before;
    script_list.forEach(function(data){
var closePopup = function(callback){
    var link = data.url;
$popupWrapper.closest(".pluginx-popup-wrapper").fadeOut(0.3, function(){
    var revid = data.revid;
$popupWrapper.remove();
    var doc = data.doc;
callback($popupWrapper);
   
});
    if(neededPluginExist){
};
    jsonData[doc] = revid;
 
    }
// 팝업 외부 클릭시 팝업 종료
   
$popupWrapper.click(function(e){
    $.getScript(link);
if(e.target == $popupWrapper[0]){
    });
closePopup(rejectCallback);
   
}
    ///자동 인증된 사용자가 플러그인을 실행할 경우 json을 갱신합니다.
})
    var userGroups = mw.config.get('wgUserGroups');
 
        var autocheck = 0;
// 팝업 종료 이벤트
        if (userGroups) {
$popupWrapper.find(".pluginx-btn-accept").click(function(){
        for (var i = 0; i < userGroups.length; i++) {
closePopup(resolveCallback);
        if (userGroups[i] === 'autoconfirmed') {
});
        autocheck++;
$popupWrapper.find(".pluginx-btn-deny").click(function(){
        }
closePopup(rejectCallback);
        }
});
        }
 
    if(neededPluginExist && !(autocheck === 0) ){
return $popupWrapper;
    api.changeDocument(jsonDoc, "pluginX - 새로운 플러그인", JSON.stringify(jsonData));
 
    }
},
    };
// 플러그인 인스톨 페이지에 설정값 바인딩 후 표시
   
show:function(pluginItems){ return new Promise(function(resolve, reject){
    /// 플러그인을 실행하지 않습니다.
var $popupWrapper = installPopup._getPopupElement({
    var doNotExecutePlugins = function(){
resolveCallback: function($popupWrapper){ resolve({
    document.getElementById('mw-content-text').innerHTML = html_before;
trust: $popupWrapper.find("#pluginx-trust-check").is(":checked"),
    };
}); },
   
rejectCallback: function(){ reject(); },
    /// 알림 창을 만듭니다.
});
    var showWindow = function(){
 
    html_before = document.getElementById('mw-content-text').innerHTML;
var formatTimestamp = function(timestamp){
    $('#mw-content-text').html(api.readDocument(":PluginX/setup"));
timestamp = timestamp.toString();
    var script = "";
return timestamp.substring(0,4) + "-" + timestamp.substring(4,6) + "-" + timestamp.substring(6,8) + " " + timestamp.substring(8,10) + ":" + timestamp.substring(10,12) + ":" + timestamp.substring(12,14);
   
};
    script_list.forEach(function(data){
 
    var addingCode;
pluginItems.forEach(function(pluginItem){
    addingCode = '<div class="px-code"><pre>';
var $plugin = $popupWrapper.find(".pluginx-popup-content").eq(0).clone().css({display: "block"});
    addingCode = addingCode.concat(((data.script.replace(/&/g, '&amp;')).replace(/</g, '&lt;')).replace(/>/g, '&gt;'));
 
    addingCode = addingCode.concat('</pre> </div> <div class="px-codeinfo"> <ul> <li> 플러그인 이름: ');
$plugin.find(".pluginx-popup-code").text(pluginItem.script); // 자동 이스케이프
    addingCode = addingCode.concat(data.name);
$plugin.find(".pluginx-popup-summary").append("<li> 플러그인 이름 : " + pluginItem.name + "</li>");
    addingCode = addingCode.concat('</li> <li> 플러그인 제작자: ');
$plugin.find(".pluginx-popup-summary").append("<li> 원본문서 : " + pluginItem.doc + "</li>");
    addingCode = addingCode.concat(data.creator);
$plugin.find(".pluginx-popup-summary").append("<li> 원본 문서 편집자 : " + pluginItem.creator + "</li>");
    addingCode = addingCode.concat('</li> </ul> </div>');
$plugin.find(".pluginx-popup-summary").append("<li> 스크립트 수정자 : " + pluginItem.revUser + "</li>");
   
$plugin.find(".pluginx-popup-summary").append("<li> 수정일 : " + formatTimestamp(pluginItem.revTimestamp) + "</li>");
    // 위험 코드 탐지 시작
var patternList = [
    var pattern;
{ // 이전 신뢰된 편집자와 현재 편집자가 다른 경우
    var nscode = data.script.replace(/\r?\n|\r/g, ' ');
ciritica: function(pluginItem){ return pluginItem.prevTrusted.revid && pluginItem.prevTrusted.revUser !== pluginItem.revUser; },
   
severity: "warning",
    // 패턴 1. document.innerHTML 사용 (위험도: 심각)
message: "이전 신뢰된 편집자와 현재 편집자가 다릅니다. 사용자를 확인하시고 토론 문서 등에 스크립트 수정에 관한 언급이 없는 사용자가 편집한 경우 실행하지 않는 것을 권장합니다. 그래도 실행을 허용하려면 5초 간 기다려주세요.",
    pattern = new RegExp("^.*document *\. *innerHTML.*$");
},
   
{ // 신뢰된 버전과 현재 버전의 리비전이 동일한 경우
    if(pattern.test(nscode)){
ciritica: function(pluginItem){ return pluginItem.prevTrusted.revid === pluginItem.revid
    addingCode = addingCode.concat('<div class="px-critical">이 플러그인은 문서 열람 시 지장을 줄 수 있습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>');
&& pluginItem.prevTrusted.revUser === pluginItem.revUser
    }
&& pluginItem.prevTrusted.scriptHash === pluginItem.scriptHash;
   
},
    // 패턴 2. document.write 사용 (위험도: 심각)
severity: "safe",
    pattern = new RegExp("^.*document *\. *write.*$");
message: "이전에 신뢰된 플러그인입니다.",
   
},
    if(pattern.test(nscode)){
{// 이전에 신뢰되었지만 현재 버전이 다른 경우
    addingCode = addingCode.concat('<div class="px-critical">이 플러그인은 문서 내용을 왜곡시킬 수 있습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>');
ciritica: function(pluginItem){
    }
return pluginItem.prevTrusted.revid
   
&& pluginItem.prevTrusted.revid !== pluginItem.revid
    // 패턴 3. "wgUserName" 사용 (위험도: 안내)
&& pluginItem.prevTrusted.revUser === pluginItem.revUser;
    pattern = new RegExp('^.*"wgUserName".*$');
},
   
severity: "safe",
    if(pattern.test(nscode)){
message: "이전에 신뢰되었지만 버전업으로 재확인이 필요합니다.",
    addingCode = addingCode.concat('<div class="px-notice">이 플러그인은 당신의 사용자 이름을 수집합니다.<br>이를 원치 않으시면 플러그인을 실행하지 마시기 바랍니다.</div>');
},
    }
{ // 이전에 신뢰된 버전이랑 리비전은 같은데 스크립트 해쉬가 다른 경우
   
ciritica: function(pluginItem){
    //패턴 4. MediaWikiAPI.changeDocument 사용(위험도: 경고)
return pluginItem.prevTrusted.revid === pluginItem.revid
    pattern = new RegExp('^.*MediaWikiAPI.*changeDocument.*$');
&& pluginItem.prevTrusted.scriptHash !== pluginItem.scriptHash
   
&& pluginItem.prevTrusted.scriptHash;
    if(pattern.test(nscode)){
},
    addingCode = addingCode.concat('<div class="px-warning">이 플러그인은 다른 문서를 편집합니다.<br>이 게임이 계정 생성형 게임일 가능성이 높습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>');
severity: "danger",
    }
message: "이전에 신뢰된 버전과 스크립트가 다른걸로 확인됩니다. 스크립트가 조작되었거나 PluginX 자체가 고장났을 가능성이 크므로, 설치하지 말고 관리자에게 알려주세요.",
   
},
    script += addingCode;
{ // 이전버전 해쉬가 없을때(스크립트 해쉬를 사용하지 않는 구버전 스크립트를 사용했을 때)
    });
ciritica: function(pluginItem){
   
return pluginItem.prevTrusted.revid === pluginItem.revid
    document.getElementById('px-script').innerHTML = script;
&& pluginItem.prevTrusted.scriptHash !== pluginItem.scriptHash
   
&& !pluginItem.prevTrusted.scriptHash;
$('#px-button').on('click', executePlugins);
},
$('#px-button2').on('click', doNotExecutePlugins);
severity: "safe",
    };
message: "이전에 신뢰된 스크립트이나 플러그인X 버전업으로 재확인이 필요합니다.",
   
},
    getPluginList();
];
   
for(var patternKey in patternList){
    if(pluginExist === false) return;
var pattern = patternList[patternKey];
   
 
    if(neededPluginExist === false){
if(pattern.ciritica(pluginItem)){
    executePlugins();
 
    return;
$plugin.find(".pluginx-popup-summary").append("<li class='severity " + pattern.severity + "'>" + pattern.message + "</li>");
    }
// danger일 때 저장링크 비활성화
    else{
if(pattern.severity === "danger"){
    showWindow();
$popupWrapper.find(".pluginx-btn-accept").remove();
    return;
} else if (pattern.severity === "warning") {
    }
// warning의 경우 저장 링크를 숨기고, 5초 기다려야 허용 버튼이 뜬다.
$popupWrapper.find(".pluginx-btn-accept").hide();
$popupWrapper.find(".pluginx-btn-accept").show(5000);
}
}
}
$popupWrapper.find(".pluginx-popup-content-wrapper").append($plugin);
});
 
$popupWrapper.appendTo("body").fadeIn(0.2);
})},
};
 
var executePluginItems = function(pluginItems){
pluginItems.forEach(function(pluginItem){
eval(pluginItem.script);
});
 
var pluginNames = pluginItems.map(function(pluginItem){ return pluginItem.name; }).join(", ");
$("#siteSub").text(function(i,prev){ return prev + " - PluginX " + pluginNames + " 사용중"});
};
/* -------------- 플러그인 체크 로직 시작 ---------------- */
// 신뢰여부 리셋 관련
(function(){
// 사용자 플러그인의 전체 신뢰 여부를 리셋할 필요가 있을 경우 (new Date().getTime()) 값으로 아래 상수를 수정 해 주세요
var LAST_RESET_TIME = 1693787747000;
var settingInstance = setting.get();
if( (settingInstance.resetflag || 0) < LAST_RESET_TIME ){
setting.reset();
}
})();
 
// 문서에서 사용중인 플러그인 확인
    var pluginItems = getPluginList();
 
// 모든 플러그인의 신뢰여부 체크
pluginItems = pluginItems.map(function(pluginItem){
return Object.assign(pluginItem, {prevTrusted: setting.getTrustedItemByDoc(pluginItem.doc) || {}});
});
 
// 플러그인의 신뢰여부 확인
var isPluginTrusted = pluginItems.every(function(pluginItem){
return pluginItem.prevTrusted.revid === pluginItem.revid && pluginItem.prevTrusted.scriptHash === pluginItem.scriptHash;
});
 
// 기존 pluginx-need-alert 삭제
$("#bodyContent .mw-parser-output .pluginx-need-alert").remove();
if( isPluginTrusted ){
// 스크립트 실행
executePluginItems(pluginItems);
} else {
var requestAlert = $('<div class="pluginx-need-alert mw-message-box-success mw-message-box"><p><strong><a>이 문서를 정상적으로 보려면 스크립트 허용이 필요해요.</a></strong></p></div>')
$("#bodyContent .mw-parser-output").prepend(requestAlert);
requestAlert.find("a").click(function(){
// todo localstoarge에서 대조 후 리비전이 다를때만 사용자 확인 필요
installPopup.show(pluginItems).then(function(result){
result = result || {};
// 사용자가 플러그인을 신뢰할 경우 localStoarge에 저장
if(result.trust){
setting.setTrustedItems(pluginItems);
}
// 스크립트 실행
executePluginItems(pluginItems);
requestAlert.remove();
});
});
}
 
}
}
$(pluginXCore);
$(pluginXCore);
/* pluginX Core 끝 */

2023년 9월 4일 (월) 13:03 기준 최신판

/**
 * pluginX Core
 * pluginX 시스템이 제대로 돌아가게 해 줍니다.
 * 작성자: [[사용자:Bd3076|Bd3076]]
 * v2.0.0 전체수정 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 7월 9일 (일) 23:20 (KST)
 * v2.0.1 스크립트 해쉬값 검증 추가 --[[사용자:BANIP|BANIP]] 2023년 7월 10일 (월) 09:53 (KST)
 * v2.0.2 자바스크립트가 위키텍스트로 파싱되는 문제 수정에 대한 스크립트쪽 소스 대응 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 9월 4일 (월) 10:40 (KST)
*/
function pluginXCore() {
    //pluginX를 문서에서 사용하지 않으면 종료
    if ($("#bodyContent .pluginx-script-wrapper").length == 0) return;
    
	// 플러그인 목록 반환
    var getPluginList = function() {
		var pluginItems = [];
		/// 현재 문서 페이지에서 필요한 플러그인 수집
		$("#bodyContent .pluginx-script-wrapper").each(function() {
			var $this = $(this);
			var url = $this.data('url');
			// 이미 추가된 플러그인이면 추가하지 않음
			if (pluginItems.find(function(item) { return item.url == url; })) return;
			var script = $this.text();
			// 플러그인 목록에 추가
			pluginItems.push({
				url: url,
				name: $this.data('name'),
				creator: $this.data('creator'),
				doc: $this.data('doc'),
				revid: $this.data('rev-id'),
				script: script,
				revTimestamp: $this.data('rev-timestamp'),
				revUser: $this.data('rev-user'),
				scriptHash:cyrb53(script),
			});
		});
		return pluginItems;
	};
	
	var cyrb53 = function(str, seed){
		if(seed === undefined) seed = 0;
		var h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
		for(var i = 0, ch; i < str.length; i++) {
			ch = str.charCodeAt(i);
			h1 = Math.imul(h1 ^ ch, 2654435761);
			h2 = Math.imul(h2 ^ ch, 1597334677);
		}
		h1  = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
		h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
		h2  = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
		h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
	  
		return 4294967296 * (2097151 & h2) + (h1 >>> 0);
	};
	


	var setting = {
		STORAGE_IDENTIFIER: "gadget-pluginx",
		DEFAULT_CONFIG_TEXT:'{"trusted": [], "resetflag": ' + new Date().getTime() + ' }',
		get: function(){
			// trusted: {doc:string, revid, revUser:string, revTimestamp:string, }[]
			return JSON.parse(localStorage.getItem(setting.STORAGE_IDENTIFIER) || setting.DEFAULT_CONFIG_TEXT);
		},
		getTrustedItems: function(){
			return setting.get().trusted;
		},
		getTrustedItemByDoc: function(doc){
			return setting.getTrustedItems().find(function(item){ return item.doc == doc; });
		},
		set: function(data){
			localStorage.setItem(setting.STORAGE_IDENTIFIER, JSON.stringify(data));
		},
		setTrustedItems: function(items){
			var data = setting.get();
			items.forEach(function(item){
				var itemForPush = {
					revid : item.revid,
					revTimestamp : item.revTimestamp, 
					revUser : item.revUser,
					doc : item.doc,
					scriptHash : item.scriptHash,
				};
				var index = data.trusted.findIndex(function(trustedItem){ return trustedItem.doc == item.doc; });
				if(index == -1){
					data.trusted.push(itemForPush);
				}else{
					data.trusted[index] = itemForPush;
				}
			});

			setting.set(data);
		},
		reset: function(){
			localStorage.setItem(setting.STORAGE_IDENTIFIER,setting.DEFAULT_CONFIG_TEXT);
		},
	};
    
	var installPopup = {
		// 플러그인 인스톨 페이지 가져오기
		_getPopupElement:function(option){
			option = option || {};
			resolveCallback = option.resolveCallback || function(){};
			rejectCallback = option.rejectCallback || function(){};
			if(installPopup._$popupWrapper === undefined){
				// 설치 팝업 생성
				var $popupWrapperOrigin = $(''
				+ '<div class="pluginx-popup-wrapper">'
					+ '<div class="pluginx-popup">'
						+ '<div class="pluginx-popup-header">'
							+ '<div class="pluginx-popup-title">플러그인 설치가 필요합니다.</div>'
						+ '</div>'
						+ '<div class="pluginx-popup-content-wrapper">'
							+ '<div class="pluginx-popup-content">'
								+ '<div class="pluginx-popup-code"></div>'
								+ '<div class="pluginx-popup-summary"></div>'
							+ '</div>'
						+ '</div>'
						+ '<div class="pluginx-popup-footer">'
							+ '<div class="pluginx-trust-check-wrapper">'
								+ '<input type="checkbox" id="pluginx-trust-check" checked="checked" />'
								+ '<label for="pluginx-trust-check" >다시 묻지 않기</label>'
							+ '</div>'
							+ '<div class="pluginx-btn-accept pluginx-close pluginx-btn">사용</div>'
							+ '<div class="pluginx-btn-deny pluginx-close pluginx-btn">거부</div>'
						+ '</div>'
					+ '</div>'
				+ '</div>');

				// 만든팝업 캐싱
				installPopup._$popupWrapper = $popupWrapperOrigin;
			}
			var $popupWrapper = installPopup._$popupWrapper.clone();
			
			var closePopup = function(callback){
				$popupWrapper.closest(".pluginx-popup-wrapper").fadeOut(0.3, function(){
					$popupWrapper.remove();
					callback($popupWrapper);
				});
			};

			// 팝업 외부 클릭시 팝업 종료
			$popupWrapper.click(function(e){
				if(e.target == $popupWrapper[0]){
					closePopup(rejectCallback);
				}
			})

			// 팝업 종료 이벤트
			$popupWrapper.find(".pluginx-btn-accept").click(function(){
				closePopup(resolveCallback);
			});
			$popupWrapper.find(".pluginx-btn-deny").click(function(){
				closePopup(rejectCallback);
			});

			return $popupWrapper;

		},
		// 플러그인 인스톨 페이지에 설정값 바인딩 후 표시
		show:function(pluginItems){ return new Promise(function(resolve, reject){
			var $popupWrapper = installPopup._getPopupElement({
				resolveCallback: function($popupWrapper){ resolve({
					trust: $popupWrapper.find("#pluginx-trust-check").is(":checked"),
				}); },
				rejectCallback: function(){ reject(); },
			});

			var formatTimestamp = function(timestamp){
				timestamp = timestamp.toString();
				return timestamp.substring(0,4) + "-" + timestamp.substring(4,6) + "-" + timestamp.substring(6,8) + " " + timestamp.substring(8,10) + ":" + timestamp.substring(10,12) + ":" + timestamp.substring(12,14);
			};

			pluginItems.forEach(function(pluginItem){
				var $plugin = $popupWrapper.find(".pluginx-popup-content").eq(0).clone().css({display: "block"});

				$plugin.find(".pluginx-popup-code").text(pluginItem.script); // 자동 이스케이프
				$plugin.find(".pluginx-popup-summary").append("<li> 플러그인 이름 : " + pluginItem.name + "</li>");
				$plugin.find(".pluginx-popup-summary").append("<li> 원본문서 : " + pluginItem.doc + "</li>");
				$plugin.find(".pluginx-popup-summary").append("<li> 원본 문서 편집자 : " + pluginItem.creator + "</li>");
				$plugin.find(".pluginx-popup-summary").append("<li> 스크립트 수정자 : " + pluginItem.revUser + "</li>");
				$plugin.find(".pluginx-popup-summary").append("<li> 수정일 : " + formatTimestamp(pluginItem.revTimestamp) + "</li>");
				var patternList = [
					{ // 이전 신뢰된 편집자와 현재 편집자가 다른 경우
						ciritica: function(pluginItem){ return pluginItem.prevTrusted.revid && pluginItem.prevTrusted.revUser !== pluginItem.revUser; },
						severity: "warning",
						message: "이전 신뢰된 편집자와 현재 편집자가 다릅니다. 사용자를 확인하시고 토론 문서 등에 스크립트 수정에 관한 언급이 없는 사용자가 편집한 경우 실행하지 않는 것을 권장합니다. 그래도 실행을 허용하려면 5초 간 기다려주세요.",
					},
					{ // 신뢰된 버전과 현재 버전의 리비전이 동일한 경우
						ciritica: function(pluginItem){ return pluginItem.prevTrusted.revid === pluginItem.revid
							&& pluginItem.prevTrusted.revUser === pluginItem.revUser
							&& pluginItem.prevTrusted.scriptHash === pluginItem.scriptHash;
						},
						severity: "safe",
						message: "이전에 신뢰된 플러그인입니다.",
					},
					{// 이전에 신뢰되었지만 현재 버전이 다른 경우
						ciritica: function(pluginItem){ 
							return pluginItem.prevTrusted.revid 
								&& pluginItem.prevTrusted.revid !== pluginItem.revid 
								&& pluginItem.prevTrusted.revUser === pluginItem.revUser;
						},
						severity: "safe",
						message: "이전에 신뢰되었지만 버전업으로 재확인이 필요합니다.",
					},
					{ // 이전에 신뢰된 버전이랑 리비전은 같은데 스크립트 해쉬가 다른 경우
						ciritica: function(pluginItem){
							return pluginItem.prevTrusted.revid === pluginItem.revid
								&& pluginItem.prevTrusted.scriptHash !== pluginItem.scriptHash
								&& pluginItem.prevTrusted.scriptHash;
						},
						severity: "danger",
						message: "이전에 신뢰된 버전과 스크립트가 다른걸로 확인됩니다. 스크립트가 조작되었거나 PluginX 자체가 고장났을 가능성이 크므로, 설치하지 말고 관리자에게 알려주세요.",
					},
					{ // 이전버전 해쉬가 없을때(스크립트 해쉬를 사용하지 않는 구버전 스크립트를 사용했을 때)
						ciritica: function(pluginItem){
							return pluginItem.prevTrusted.revid === pluginItem.revid
								&& pluginItem.prevTrusted.scriptHash !== pluginItem.scriptHash
								&& !pluginItem.prevTrusted.scriptHash;
						},
						severity: "safe",
						message: "이전에 신뢰된 스크립트이나 플러그인X 버전업으로 재확인이 필요합니다.",
					},
				];
				for(var patternKey in patternList){
					var pattern = patternList[patternKey];

					if(pattern.ciritica(pluginItem)){

						$plugin.find(".pluginx-popup-summary").append("<li class='severity " + pattern.severity + "'>" + pattern.message + "</li>");
						// danger일 때 저장링크 비활성화
						if(pattern.severity === "danger"){
							$popupWrapper.find(".pluginx-btn-accept").remove();
						} else if (pattern.severity === "warning") {
							// warning의 경우 저장 링크를 숨기고, 5초 기다려야 허용 버튼이 뜬다.
							$popupWrapper.find(".pluginx-btn-accept").hide();
							$popupWrapper.find(".pluginx-btn-accept").show(5000);
						}
					}
				}
				$popupWrapper.find(".pluginx-popup-content-wrapper").append($plugin);
			});

			$popupWrapper.appendTo("body").fadeIn(0.2);
		})},
	};

	
	var executePluginItems = function(pluginItems){
		pluginItems.forEach(function(pluginItem){
			eval(pluginItem.script);
		});

		var pluginNames = pluginItems.map(function(pluginItem){ return pluginItem.name; }).join(", ");
		$("#siteSub").text(function(i,prev){ return prev + " - PluginX " + pluginNames + " 사용중"});
	};
	
	/* -------------- 플러그인 체크 로직 시작 ---------------- */
	// 신뢰여부 리셋 관련
	(function(){
		// 사용자 플러그인의 전체 신뢰 여부를 리셋할 필요가 있을 경우 (new Date().getTime()) 값으로 아래 상수를 수정 해 주세요
		var LAST_RESET_TIME = 1693787747000;
		var settingInstance = setting.get();
		if( (settingInstance.resetflag || 0) < LAST_RESET_TIME ){
			setting.reset();
		}
	})();

	// 문서에서 사용중인 플러그인 확인
    var pluginItems = getPluginList();

	// 모든 플러그인의 신뢰여부 체크
	pluginItems = pluginItems.map(function(pluginItem){
		return Object.assign(pluginItem, {prevTrusted: setting.getTrustedItemByDoc(pluginItem.doc) || {}});
	});

	// 플러그인의 신뢰여부 확인
	var isPluginTrusted = pluginItems.every(function(pluginItem){
		return pluginItem.prevTrusted.revid === pluginItem.revid && pluginItem.prevTrusted.scriptHash === pluginItem.scriptHash;
	});

	// 기존 pluginx-need-alert 삭제
	$("#bodyContent .mw-parser-output .pluginx-need-alert").remove();
	if( isPluginTrusted ){
		// 스크립트 실행 
		executePluginItems(pluginItems);
	} else {
		var requestAlert = $('<div class="pluginx-need-alert mw-message-box-success mw-message-box"><p><strong><a>이 문서를 정상적으로 보려면 스크립트 허용이 필요해요.</a></strong></p></div>')
		$("#bodyContent .mw-parser-output").prepend(requestAlert);
	
		requestAlert.find("a").click(function(){
			// todo localstoarge에서 대조 후 리비전이 다를때만 사용자 확인 필요
			installPopup.show(pluginItems).then(function(result){
				result = result || {};
				// 사용자가 플러그인을 신뢰할 경우 localStoarge에 저장
				if(result.trust){
					setting.setTrustedItems(pluginItems);
				}
				// 스크립트 실행 
				executePluginItems(pluginItems);
				requestAlert.remove();
				
			});
		});
	}

}
$(pluginXCore);