MediaWiki:Gadget-MassProtect.js
if (ep.tag) {
// 保護に成功した場合のみ
if (succeeded) {
// タグ添付プロセスに移行
return pasteTag(pagetitle);
// 保護に失敗している場合は
} else {
// 進捗を「タグ: 中止 (保護失敗)」に更新
updateProgress(pagetitle, 'tag', '保護失敗', true);
}
}
// タグ添付をしない、もしくは保護に失敗した場合は
if (ep.pages[protectCnt]) protectPages(); // 次の保護プロセスに移行
});
}
}
/**
* タグ添付を実行する関数
* @param {string} pagetitle
*/
function pasteTag(pagetitle) {
// タグ添付対象がモジュール名前空間のページなら
if (ep.tag !== 'rm' && pagetitle.match(/^(?:モジュール|[Mm]odule):/)) {
// 進捗を「中止 (モジュール)」にして
updateProgress(pagetitle, 'tag', 'モジュール', true);
// タグ添付をせずに次の保護処理に移行
if (ep.pages[protectCnt]) protectPages();
return;
}
// タグ添付対象ページの最新版を取得
$.get(apiEndpoint, {
action: 'query',
titles: pagetitle,
prop: 'info|revisions',
rvprop: 'content',
rvslots: 'main',
curtimestamp: 1,
format: 'json'
}, function(res) {
var resPages;
// APIリクエストの返り値にページの編集に必要な情報がなければ
if (!res || !res.query || !(resPages = res.query.pages)) {
// 進捗を「タグ: 失敗 (通信エラー)」に更新し、次の保護処理に移行
updateProgress(pagetitle, 'tag', '通信エラー');
if (ep.pages[protectCnt]) protectPages();
return;
}
// ページの編集に必要な情報を取得
var curtimestamp = res.curtimestamp; // 最新版を取りに行った時間のタイムスタンプ (編集競合対策)
for (var key in resPages) { // キーはランダムナンバー (ページID)
var resObj = resPages[key];
var isRedirect = resObj.redirect === ; // リダイレクトか否か
var basetimestamp = resObj.touched; // 最新版のタイムスタンプ (編集競合対策)
var content = resObj.revisions[0].slots.main['*']; // ページ内容
var originalContent = JSON.parse(JSON.stringify(content)); // 後で比較する用の魚拓
// ページ内に除去が必要なテンプレートがあれば除去
var templates;
if (ep.tag === 'rm') { // 除去対象のテンプレートを取得
templates = findTemplates(content, ['保護依頼']);
} else {
templates = findTemplates(content, MassProtect.template.names, MassProtect.template.prefixes);
}
if (!templates) { // ページ内に処理不能な波括弧がある場合
updateProgress(pagetitle, 'tag', '処理不能な波括弧を検出', true);
if (ep.pages[protectCnt]) protectPages();
return;
}
if (templates.length !== 0) { // 除去対象テンプレートがあれば
// 正規表現内で使用するため特殊文字をエスケープ
templates = templates.map(function(item) {
return mw.util.escapeRegExp(item);
});
// ページ内の「TEMPLATE\n」を空の文字列に置換
var regex = new RegExp('(?:' + templates.join('|') + ')[^\\S\\n\\r]*\\n?', 'g');
content = content.replace(regex, );
// テンプレートを除去後、文字列を含まない noinclude タグなどがある場合は除去
content = content.replace(/<noinclude>(?:\s)*?<\/noinclude>[^\S\n\r]*\n?/gm, ).replace(/\/\*(?:\s)*?\*\/[^\S\n\r]*\n?/gm, );
}
// 冒頭に保護タグを挿入 (リダイレクトとテンプレート名前空間のページは別処理が必要)
if (ep.tag !== 'rm') {
if (isRedirect) { // リダイレクトなら
// リダイレクトのリンクの前に改行があることがあるのでそれを除去
content = content.trim();
// ページ内容が転送リンクの一行のみなら
if (content.indexOf('\n') === -1) {
// 2行目にタグを挿入
content += '\n' + ep.tag;
// 複数行あれば
} else {
// 1つ目の改行の後にタグを挿入
content = content.replace('\n', '\n' + ep.tag + '\n');
}
} else { // リダイレクトでなければ
var linebreak;
// Template名前空間のcssページなら
if (pagetitle.match(/^(?:[Tt]emplate|テンプレート):.+\.css$/)) {
// コメントアウトしたタグを挿入
content = '/* ' + ep.tag + ' */' + '\n' + content;
// それ以外のTemplate名前空間のページ、またはWikipedia名前空間内に位置するページのサブページの場合
} else if (pagetitle.match(/^(?:[Tt]emplate|テンプレート):/) || (linebreak = needNoinclude(pagetitle))) {
// トランスクルードさせずにタグを挿入
content = '<noinclude>' + ep.tag + '</noinclude>' + (linebreak ? '\n' : ) +  content;
// Template名前空間以外のページなら
} else {
// 単純に1行目にタグを挿入
content = ep.tag + '\n' + content;
}
}
}
}
// 編集前と編集後で同じ内容になる場合は編集しない
if (content === originalContent) {
var msg = ep.tag === 'rm' ? 'タグなし' : '同じ内容';
updateProgress(pagetitle, 'tag', msg, true);
if (ep.pages[protectCnt]) protectPages();
return;
}
// ページの編集
var summary = (ep.tag === 'rm' ? '-保護依頼' : ep.tag) + ' (MassProtect)';
$.post(apiEndpoint, {
action: 'edit',
title: pagetitle,
text: content,
summary: summary,
minor: 1,
basetimestamp: basetimestamp,
starttimestamp: curtimestamp,
nocreate: 1,
watchlist: 'nochange',
token: mw.user.tokens.get('csrfToken'),
format: 'json'
}, function(res2) {
// 何らかの理由で編集に失敗
if (res2.error) {
// 進捗を「タグ: 失敗 (エラーの詳細)」に更新
updateProgress(pagetitle, 'tag', res2.error.info);
// 不明なエラー
} else if (!res2 || !res2.edit) {
// 進捗を「タグ: 失敗 (不明なエラー)」に更新
updateProgress(pagetitle, 'tag', false);
// それ以外
} else {
// 成功
if (res2.edit.result === 'Success') {
// 進捗を「タグ: 成功」に更新
updateProgress(pagetitle, 'tag', true);
// 失敗 (res2.edit.result === 'Failure')、キャプチャの手動入力が求められた時などにこの値が返ってくるが、管理者は無関係のため通常はこのコードには辿り着かない
} else {
// 進捗を「タグ: 失敗 (Failure)」に更新
updateProgress(pagetitle, 'tag', res2.edit.result);
}
}
// まだ保護対象ページが残っている場合は次の保護プロセスに移行
if (ep.pages[protectCnt]) protectPages();
});
});
}
/**
* あるページに保護タグを添付する際に <noinclude> で囲むべきかを判別する関数
* @param {string} pagetitle
* @returns {boolean} 対象ページがWikipedia名前空間にあり、サブページの場合のみtrue
*/
function needNoinclude(pagetitle) {
return pagetitle.match(/^Wikipedia:[^#\/]+?\/[^#]+/i) ? true : false;
}
});
/**
* window.confirm()の代わり
* @param {string} dialogContent
* @returns {jQuery.Promise<boolean>}
*/
function dconfirm(dialogContent) {
var def = new $.Deferred();
$('body').append(
+
dialogContent +
);
$('#mp-confirm').dialog({
resizable: false,
height: 'auto',
width: 'auto',
minWidth: 500,
modal: true,
position: {
my: 'center',
at: 'center'
},
buttons: [
{
text: 'はい',
click: function() {
$(this).dialog('destroy').remove();
def.resolve(true);
}
},
{
text: 'いいえ',
click: function() {
$(this).dialog('destroy').remove();
def.resolve(false);
}
}
]
});
return def.promise();
}
/**
* 一括保護の進捗状態を更新する関数
* @param {string} page 保護対象ページ名
* @param {string} type 'protect' または 'tag'
* @param {boolean|string} result 成功したらtrue、不明なエラーが発生したらfalse、理由の分かるエラーが発生したらその詳細 (または「中止 (XXX)」のXXXにあたる文字列)
* @param {boolean} [cancelled] trueの場合「中止 (XXX)」を表示
*/
function updateProgress(page, type, result, cancelled) {
/********************************************************************************************\
'<fieldset>' +
'<legend>進捗</legend>' +
'<p>' +
`${page} ` +
`保護: ${MassProtect.image.doing} ` +
`タグ: ${MassProtect.image.doing}` +
'</p>' +
'</fieldset>'
\********************************************************************************************/
// 更新対象のセレクタ
var $progress = $('.mp-progress-page').filter(function() {  // mp-progress-page のクラス属性を持っている  タグを
return $(this).text() === page;         // 特定のページ名を text として持つものに絞り込み
}).siblings('.mp-progress-' + type);        // 姉妹関係にあるどちらかの  が更新対象のセレクタ
// セレクタの置換用メッセージ
var progressMsg = {
done: '完了',
failed: '失敗 (' + result + ')',
cancelled: '中止 (' + result + ')'
};
// 更新
switch (result) {
case true:
$progress.prop('outerHTML', progressMsg.done);
break;
case false:
$progress.prop('outerHTML', progressMsg.failed);
break;
default:
if (cancelled) {
$progress.prop('outerHTML', progressMsg.cancelled);
} else {
$progress.prop('outerHTML', progressMsg.failed);
}
}
}
/**
* ウィキテキストからテンプレートを抽出する関数
* @param {string} wikitext テンプレートを検索する対象のウィキテキスト
* @param {string|Array} [templateName] テンプレート名で絞り込み
* @param {string|Array} [templatePrefix] テンプレートの接頭辞で絞り込み
* @returns {Array|null} nullの場合はエラー
*/
function findTemplates(wikitext, templateName, templatePrefix) {
// 原文を ' wikitext.split( の数
var temp = ; // 値をよけておく用
// 配列要素内に  がない (= 他のテンプレートをネストしている)
if (tempTailCnt === 0) {
nest.push(i); // 配列要素のインデックスを記録
// 配列要素内に '}' が1つある (= '}' の左側の文字列がテンプレートの引数群)
} else if (tempTailCnt === 1) {
temp =  + tempInnerContent[i].split()[0] + '}';
if ($.inArray(temp, templates) === -1) templates.push(temp);
// 配列要素内に '}' が2つ以上ある (例: 'TL2|...}...}'; = 他のテンプレートがネストされている)
} else {
for (var j = 0; j < tempTailCnt; j++) {
if (j === 0) { // 一番内側のテンプレート
temp =  + tempInnerContent[i].split()[j] + '}'; // 配列要素内に '}' が1つある時と同じ
if ($.inArray(temp, templates) === -1) templates.push(temp);
} else { // 多重にネストされたテンプレート
var elNum = nest[nest.length -1]; // ネストの始まり部分の配列要素インデックス
nest.pop(); // ネストインデックスは1度参照したら用済み
var nestedTempInnerContent = tempInnerContent[i].split('}'); // '}' でsplitした新しい配列を作成
temp = '{' + tempInnerContent.slice(elNum, i).join() +  + nestedTempInnerContent.slice(0, j + 1).join() + ;
if ($.inArray(temp, templates) === -1) templates.push(temp);
}
}
}
})();
} // ループ終了時には templates 配列に wikitext 内の全てのテンプレートが格納されている
// コメントアウトされたテンプレートなど、トランスクルード状態にないテンプレートを配列から除去
var co = extractCommentOuts(wikitext);
if (co) {
co.forEach(function(item) {
templates = templates.filter(function(template) {
return item.indexOf(template) === -1; // コメントアウト部に含まれないテンプレートのみを残す
});
});
}
// テンプレートの絞り込みが不要、またはウィキテキスト内にテンプレートがない場合はここで終了
if ((!templateName && !templatePrefix) || templates.length === 0) return templates;
// 関数が受け取った引数が string の場合 Array に変換 (このスクリプト内では配列しか渡されないがこの関数がコピペ使用された時用)
if (templateName && typeof templateName === 'string') templateName = [templateName];
if (templatePrefix && typeof templatePrefix === 'string') templatePrefix = [templatePrefix];
/**
* テンプレートの1文字目は大文字小文字の区別がないためその正規表現を作る関数
* @param {string} str
* @returns {string} [Xx]
*/
var caseInsensitiveFirstLetter = function(str) {
return '[' + str.substring(0, 1).toUpperCase() + str.substring(0, 1).toLowerCase() + ']';
};
// テンプレートの絞り込み用の正規表現を作成
var names = [], prefixes = [];
if (templateName) {
for (var i = 0; i < templateName.length; i++) {
names.push(caseInsensitiveFirstLetter(templateName[i]) + mw.util.escapeRegExp(templateName[i].substring(1)));
}
var templateNameRegExp = new RegExp('^(' + names.join('|') + ')$');
}
if (templatePrefix) {
for (var i = 0; i < templatePrefix.length; i++) {
prefixes.push(caseInsensitiveFirstLetter(templatePrefix[i]) + mw.util.escapeRegExp(templatePrefix[i].substring(1)));
}
var templatePrefixRegExp = new RegExp('^(' + prefixes.join('|') + ')');
}
// テンプレートの絞り込み
var errHandler = false;
templates = templates.filter(function(item) {
var name = item.match(/^\{2}\s*([^\|\{\}\n]+)/); //  ...  の TEMPLATENAME を抽出
if (!name) {
errHandler = true;
return;
}
name = name[1].trim();
if (templateName && templatePrefix) {
return name.match(templateNameRegExp) || name.match(templatePrefixRegExp);
} else if (templateName) {
return name.match(templateNameRegExp);
} else if (templatePrefix) {
return name.match(templatePrefixRegExp);
}
});
if (errHandler) templates = null;
return templates;
}
/**
* , , <pre />, , and  に囲まれた文字列をウィキテキストから抽出
* @param {string} wikitext
* @returns {Array|null}
*/
function extractCommentOuts(wikitext) {
return wikitext.match(/(|[\s\S]*?<\/nowiki>|<pre[\s\S]*?<\/pre>||)/gm);
}
// ****************************************************************************
})(mediaWiki, jQuery);
//