MediaWiki:Gadget-MassProtect.js
return acc;
}, []);
}).catch(function(code, err) {
console.log(err);
return [];
});
};
/**
* @returns {JQueryPromise<Array>}
*/
var getLtaList = function() {
var ltalist = [];
/**
* @param {string} [apcontinue]
* @returns {JQueryPromise<void>}
*/
var query = function(apcontinue) { // 500件を超えてマッチする場合ループが必要なため、内部処理関数を作成
// 'LTA:' から始まるページリストを取得
return api.get({
action: 'query',
list: 'allpages',
apprefix: 'LTA:',
apnamespace: '0',
apfilterredir: 'redirects',
aplimit: 'max',
apcontinue: apcontinue,
format: 'json'
}).then(function(res) {
var resPages;
if (!res || !res.query || !(resPages = res.query.allpages) || !resPages.length) return;
resPages.forEach(function(obj) {
// 'LTA:NAME' を配列にプッシュ ('LTA:' のみのものおよび 'LTA:XXX/YYY' のサブページは除外)
if (/^LTA:[^/]+$/.test(obj.title)) ltalist.push( + obj.title + );
});
var resCont;
return res.continue && (resCont = res.continue.apcontinue) ? query(resCont) : undefined;
}).catch(function(code, err) {
console.log(err);
return;
});
};
// 全てのクエリが終わったら配列を返す
return query().then(function() {
return ltalist;
});
};
// 上で定義したAPIリクエストを送信
var deferreds = [];
deferreds.push(getVipList(), getLtaList());
return $.when.apply($, deferreds).then(function(viplist, ltalist) { // 両方の値が返ってきたら
var list = viplist.concat(ltalist); // 返り値の配列をマージ
// 非定形保護理由にオートコンプリート形式で表示
$('#mp-reason-custom').autocomplete({
source: function(req, res) { // 表示件数を10件に絞る (項目数が多すぎるため)
var results = $.ui.autocomplete.filter(list, req.term);
res(results.slice(0, 10));
},
position: { // テキストボックスの上に表示
my: 'left bottom',
at: 'left top'
}
})
.attr('placeholder', '非定型の保護理由 (VIP・LTA名称を入力すると候補が表示されます)');
});
}
/**
* 保護対象ページインプットボックス内の重複分、赤点スペース、余分な改行を除去・整形 + ページ一覧を取得する関数
* @returns {Array} 保護対象ページタイトルの配列
*/
function getTargetPages() {
// インプットボックスの値を取得し、赤点スペースおよび余分な改行を除去
var $input = $('#mp-targetpages');
var inputVal = $input.val().replace(/\u200e/g, ).trim();
// 整形されたインプットボックスの値が空の文字列でなければ
if (inputVal) {
// 値を改行で分割し配列を作成
inputVal = inputVal.split('\n');
inputVal = inputVal.map(function(item) { // 配列要素のそれぞれから赤点スペースを除去の上アンダーバーを半角スペースに置換、前後の余分なスペースを除去
return item.replace(/\u200e/g, ).replace(/_/g, ' ').trim();
}).filter(function(item, index, arr) { // 空の文字列と重複要素を配列から除去
return item !==  && arr.indexOf(item) === index;
});
// 整形したSplit配列を使ってインプットボックスの値を更新
$input.val(inputVal.join('\n'));
// 値が空の文字列なら
} else {
inputVal = []; // Return用に空の配列を代入
}
// 保護対象のページ数表示を更新
$('#mp-targetpages-count').text(inputVal.length);
// 保護対象ページ群の配列をreturn
return inputVal;
}
// 「整形」ボタンを押した際に保護対象ページインプットボックス内を整理
$(document).off('click', '#mp-targetpages-cleanup').on('click', '#mp-targetpages-cleanup', getTargetPages);
// 「投稿記録からページ名を取得」フィールドの「取得」ボタン
$(document).off('click', '#mp-contribs-search-submit').on('click', '#mp-contribs-search-submit', function() {
var getDeletedContribs = $('#mp-contribs-search-deleted').is(':checked');
// 利用者名の評価
var username = $('#mp-contribs-search-input').val().trim().replace(/ /g, '_');
if (!username) return alert('利用者名が入力されていません');
if (getDeletedContribs && mw.util.isIPAddress(username, true) && username.match(/\/\d+$/)) {
return alert('IPレンジの削除された投稿記録は取得できません');
}
username = encodeURIComponent(username);
// 期間設定の取得
var ucstart = $('#mp-contribs-search-ucstart').val().replace(/_/g, ' ').trim(),
ucend = $('#mp-contribs-search-ucend').val().replace(/_/g, ' ').trim(),
dateFormat = /^\d{4}-\d{2}-\d{2}$/;
if ((ucstart && !ucstart.match(dateFormat)) || (ucend && !ucend.match(dateFormat))) {
return alert('期間設定のフォーマットが不正です (YYYY-MM-DD)');
}
if (ucstart && ucend) {
if (new Date(ucstart) > new Date(ucend)) return alert('期間設定が不正です'); // 期間の始まりが期間の終わりよりも後の場合
if (ucstart === ucend) ucend = null; // start= と end= が同じ場合全ての投稿記録を返してしまうためその対策
}
// メッセージを表示
$('#mp-contribs-search-submit').prop('disabled', true);
$('#mp-contribs-search-progress').prop('innerHTML',
'取得しています' + MassProtect.image.doing
);
// URLを取得
var url = mw.config.get('wgServer') + mw.config.get('wgScript');
url += '?title=Special:' + (getDeletedContribs ? 'Deleted' : ) + 'Contributions' + '&target=' + username + '&limit=500';
url += (ucstart ? '&start=' + ucstart : ) + (ucend ? '&end=' + ucend : );
/**
* 投稿記録からページ名を取得する関数
* @return {jQuery.Promise<Array|string>} 取得に成功した場合はArray、失敗した場合はエラーメッセージのstring
*/
var getPagetitlesFromContribs = function() {
var def = new $.Deferred();
$.get(url)
.then(function(scraped) {
// 投稿記録が取得できたかを判定
if (!scraped) return def.resolve('通信エラーが発生しました');
var $contribs = $(scraped).find('.mw-contributions-list').children('li');
if ($contribs.length === 0) return def.resolve('投稿記録が見つかりませんでした');
// 投稿記録を配列化
var pagetitles = [];
var clss = getDeletedContribs ? '.mw-changeslist-title' : '.mw-contributions-title';
$contribs.each(function() {
var title = $(this).find(clss).text();
if ($.inArray(title, pagetitles) === -1) pagetitles.push(title);
});
def.resolve(pagetitles);
}).catch(function(err) {
console.log(err);
def.resolve('利用者が見つかりませんでした');
});
return def.promise();
};
getPagetitlesFromContribs().then(function(res) {
// 失敗した場合
if (typeof res === 'string') {
$('#mp-contribs-search-progress').prop('innerHTML',
res + MassProtect.image.failed
);
return;
}
// 成功した場合
var originalTitles = getTargetPages();
var delimiter = originalTitles.length === 0 ?  : '\n';
var obtainedTitles = JSON.parse(JSON.stringify(res)).filter(function(el) { return originalTitles.indexOf(el) === -1; });
var erasedCnt = res.length - obtainedTitles.length;
erasedCnt = erasedCnt !== 0 ? ' (うち' + erasedCnt + 'ページは入力済みです)' : ;
var pages = originalTitles.join('\n') + delimiter + obtainedTitles.join('\n');
$('#mp-targetpages').val(pages);
$('#mp-targetpages-count').text(parseInt($('#mp-targetpages-count').text()) + obtainedTitles.length);
$('#mp-contribs-search-progress').prop('innerHTML',
res.length + 'ページを取得しました' + erasedCnt + MassProtect.image.done
);
}).then(function() {
setTimeout(function() {
$('#mp-contribs-search-progress').empty();
$('#mp-contribs-search-submit').prop('disabled', false);
}, 2500);
});
});
// 「ページ情報検索」の値が更新されたらページ情報を表示
var inputboxUpdateTimeout;
$(document).off('input', '#mp-targetpages-search-input').on('input', '#mp-targetpages-search-input', function() {
/*******************************************************************************************************\
+
+
'ページ情報検索 ' +
'<input>' +
'<p>' +
' (<a>履歴</a> /' +
' <a>記録</a> /' +
' <a>保護記録</a>)' +
'</p>' +
+
+
'<p></p>' +
+
+
'<p>未作成のページ</p>' +
+
\*******************************************************************************************************/
// jQueryオブジェクトを変数に格納
var $input = $(this),
$links = $(this).siblings('p'),
$statusDiv = $('#mp-targetpages-search-status-div'),
$status = $statusDiv.children('p'),
$missingDiv = $('#mp-targetpages-search-missing-div');
// 既にinputイベントがトリガーされている際に、秒数カウントをリセットしタイピング終了後3.5秒で1度だけイベントをトリガー
clearTimeout(inputboxUpdateTimeout);
inputboxUpdateTimeout = setTimeout(function() {
// 入力されたページ名を取得
var pagename = $input.val().replace(/\u200e/g, ).trim();
// インプットボックスが空白化されたら全てのリンクを非表示に
if (!pagename) {
$links.css('display', 'none');
$statusDiv.css('display', 'none');
$missingDiv.css('display', 'none');
return;
}
// インプットボックスに有効な値が入力されたら
$links.css('display', 'inline-block'); // リンクを表示し
$links.children('a').eq(0).attr('href', mw.util.getUrl(pagename, {action: 'history'})); // リンク先を更新
$links.children('a').eq(1).attr('href', mw.util.getUrl('Special:Log', {page: pagename}));
$links.children('a').eq(2).attr('href', mw.util.getUrl('Special:Log', {type: 'protect', page: pagename}));
mw.hook('wikipage.content').fire(mw.util.$content);
// 現在の保護状態のチェック
getProtectionStatus(pagename).then(function(obj) {
$statusDiv.css('display', 'block'); // 保護状態表示用divを表示
// 保護設定を表示
var displayText;
if (!obj) {
displayText = '取得に失敗しました';
} else {
if (obj.protection) {
displayText = obj.protection.overview.join(', ');
} else {
displayText = '保護設定なし';
}
}
$status.css('color', 'orange').prop('innerHTML', displayText);
$status.animate({color: 'black'}, 1000); // 値の更新時に一瞬テキストに色を付け、すぐ元に戻す
// 未作成のページなら、追加のメッセージを表示
if (obj && obj.missing) {
$missingDiv.css('display', 'block');
} else {
$missingDiv.css('display', 'none');
}
});
}, 350);
});
/**
* ページの最新の保護記録を取得する関数
* @param {string} pagetitle
* @returns {jQuery.Promise<{title: string, missing: boolean, protection: {overview: Array, details: Array}, executer: string, timestamp: string, reason: string}|undefined>}
* 保護記録がない場合 title と missing のみ、クエリに失敗した場合は undefined
*/
function getProtectionStatus(pagetitle) {
// 関数を非同期化
var def = new $.Deferred();
// APIリクエスト
api.get({
action: 'query',
titles: pagetitle,
prop: 'info',
inprop: 'protection',
list: 'logevents',
letype: 'protect',
letitle: pagetitle,
lelimit: 3,
formatversion: '2'
}).then(function(res) {
var resPages;
if (!res || !res.query || !(resPages = res.query.pages) || (Array.isArray(resPages) && resPages.length === 0)) return def.reject();
resPages = resPages[0];
/*
protectionStatus = {
title: pagetitle,
missing: boolean,
protection: {
overview: ["編集半保護 (2022-09-24T18:32:15まで)", "移動半保護 (2022-09-24T18:32:15まで)"],
details: [
{
"type": "edit",
"level": "autoconfirmed",
"expiry": "2022-09-24T18:32:15Z"
},
{
"type": "move",
"level": "autoconfirmed",
"expiry": "2022-09-24T18:32:15Z"
}
]
},
executer: '保護した管理者の利用者名',
timestamp: '保護された時刻',
reason: '保護された理由'
}
*/
// 保護あり・なし共通のプロパティ
var protectionStatus = {
title: pagetitle,
missing: resPages.missing ? true : false
};
// 現在保護されている場合、追加のプロパティを取得
var resPrt;
if ((resPrt = resPages.protection) && resPrt.length !== 0) { // 現在保護されている場合
(function() {
// 当日に保護が解除されたばかりだとAPIは保護されていないページも res.query.pages.protection に要素のある配列を返すためその対策
var d = new Date();
var isProtected = resPrt.some(function(obj) {
if (!obj.expiry) return false;
if (obj.expiry.match(/^in/)) return true;
var protectedUntil = new Date(obj.expiry);
return d < protectedUntil;
});
if (!isProtected) return;
// 保護設定を取得
var protectionLevels = resPrt.reduce(function(acc, obj) {
var level = ;
level += MassProtect.translate.type[obj.type] ? MassProtect.translate.type[obj.type] : '??';
level += MassProtect.translate.level[obj.level];
level += ' (' + (obj.expiry.match(/^in/) ? '無期限' : obj.expiry.replace(/Z$/, ) + 'まで') + ')';
acc.push(level);
return acc;
}, []);
protectionStatus = Object.assign(protectionStatus,
{
protection: {
overview: protectionLevels,
details: resPrt
}
}
);
// より詳細な情報を取得可能な場合は取得
var additionalInfo = {};
var failed = '取得失敗';
var resLgev;
if ((resLgev = res.query.logevents) && resLgev.length !== 0) {
resLgev.some(function(obj) {
var action;
if (!(action = obj.action) || action === 'unprotect') {
return false;
} else {
additionalInfo = {
executer: obj.user ? obj.user : failed,
timestamp: obj.timestamp ? obj.timestamp : failed,
reason: typeof obj.comment !== 'undefined' ? obj.comment : failed,
};
return true;
}
});
protectionStatus = Object.assign(protectionStatus, additionalInfo);
}
})();
}
def.resolve(protectionStatus);
}).catch(function(code, err) {
console.log(err.error.info);
def.reject();
});
return def.promise();
}
// 保護レベルフィールドの「区分」selectの値が変わったら、不可視の内部フィールドを表示し可視の内部フィールドを隠す
$(document).off('change', '#mp-level-division').on('change', '#mp-level-division', function() {
var $visibleFields = $('.mp-level-settings:visible');
var $invisibleFields = $('.mp-level-settings:hidden');
$visibleFields.css('display', 'none');
$invisibleFields.css('display', 'block');
});
// 「追加保護オプションのロックを解除」が(アン)チェックされたら該当の保護レベル設定フィールドを無効化・有効化
$(document).off('change', '#mp-level-enableadditionaloptions').on('change', '#mp-level-enableadditionaloptions', function() {
$('#mp-level-move, #mp-level-upload').find('select, input').prop('disabled', !$(this).is(':checked'));
});
// ------ 追加オプションが未開放の場合移動保護とアップロード保護の設定を編集保護の設定と同一のものに自動的に変更 ------
// レベルドロップダウン
$(document).off('change', '#mp-level-edit-level-select').on('change', '#mp-level-edit-level-select', function() {
if ($('#mp-level-enableadditionaloptions').is(':checked')) return;
var selectedVal = $(this).children('option').filter(':selected').val();