「利用者:Dragoniez/scripts/AN Reporter.js」の版間の差分

削除された内容 追加された内容
m Dragoniez がページ「利用者:Dragoniez/AN Reporter.js」を「利用者:Dragoniez/scripts/AN Reporter.js」に移動しました
v7.0: ライブラリからの読み込み形式に変更、一部機能を高速化など (機能上の変更は特になし)
2行目:
* AN Reporter (ANR) *
* Author: Dragoniez *
* Version: 67.4.10 *
************************************/
//<nowiki>
45行目:
'portletLink': false,
'causeIntentionalError': false,
'library': false, // Load local dragoLib if true
'drPreviewSections': 'tarSectionsS' // I, S, 3RR, SubpagedLTA
};
const scriptAd = debugMode.scriptAd ? ' ([[User:Dragoniez/AN Reporter|' + (debugMode.scriptAd ? 'AN Reporter Experimental]])' : ' ([[User:Dragoniez/AN Reporter|AN Reporter]])');
const portletLinkText = debugMode.portletLink ? '報告β' : '報告';
const developerLink = `<a href="${mw.util.getUrl('User talk:Dragoniez/AN Reporter')}" target="_blank">開発者</a>`;
const library = debugMode.library ?
'http://127.0.0.1:5500/dragoLib/dragoLib.js' :
'//ja.wikipedia.org/w/index.php?title=User:Dragoniez/scripts/dragoLib.js&action=raw&ctype=text/javascript';
 
// Dialog designs
var fontSize, select2FontSize, lkPosition;
if (anrConfig.fontSize) { // Font size
fontSize = anrConfig.fontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
fontSize = '80%';
break;
case 'monobook':
fontSize = '110%';
break;
case 'timeless':
fontSize = '90%';
break;
default:
fontSize = '80%';
}
}
if (anrConfig.dropdownFontSize) { // Font size of select2 dropdown
select2FontSize = anrConfig.dropdownFontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
select2FontSize = '0.9em';
break;
case 'monobook':
select2FontSize = '1.03em';
break;
case 'timeless':
select2FontSize = '0.94em';
break;
default:
select2FontSize = '0.9em';
}
}
if (anrConfig.portletlinkPosition) { // Position of the portletlink
lkPosition = anrConfig.portletlinkPosition;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
lkPosition = 'p-views';
break;
case 'minerva':
lkPosition = 'p-personal';
break;
default: // monobook, timeless, or something else
lkPosition = 'p-cactions';
}
}
 
// Page names
71 ⟶ 131行目:
var userDiv; // What to append when the 'add' button is hit
var userCnt = 1; // *ID number of the elements in the appended userDiv
const mainDialogButtons = [{ // Buttons
'text': '報告',
'click': report
}, {
'text': 'プレビュー',
'click': preview
}, {
'text': '閉じる',
'click': function() {
$(this).dialog('close');
}
}];
 
 
78 ⟶ 150行目:
$.when(
$.getScript('https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js'),
$.getScript(library),
mw.loader.using(['jquery.ui', 'mediawiki.util']),
mw.loader.using('jquery.ui'),
$.ready
).then(function(){
86 ⟶ 159行目:
 
// Run the script only if the user is autoconfirmed and the page is not an edit page
if (isInArraydragoLib.inGroup('autoconfirmed', mw.config.get('wgUserGroups')) && mw.config.get('wgAction') !== 'edit') {
addAnrPortletLink();// Add a portletlink for ANR
$(mw.util.addPortletLink(lkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog);
}
 
94 ⟶ 168行目:
 
// ******************** MAIN FUNCTIONS ********************
 
function addAnrPortletLink() {
 
// Define the position of the portletlink (skin-dependent)
var lkPosition;
if (anrConfig.portletlinkPosition) {
lkPosition = anrConfig.portletlinkPosition;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
lkPosition = 'p-views';
break;
case 'minerva':
lkPosition = 'p-personal';
break;
default: // monobook, timeless, or something else
lkPosition = 'p-cactions';
}
}
 
// Add a portletlink for ANR
$(mw.util.addPortletLink(lkPosition, '#', portletLinkText, 'ca-anr', '管理者伝言板に利用者を報告', null, '#ca-move')).click(openAnrDialog);
 
}
 
// Set font size
var fontSize, select2FontSize
if (anrConfig.fontSize) {
fontSize = anrConfig.fontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
fontSize = '80%';
break;
case 'monobook':
fontSize = '110%';
break;
case 'timeless':
fontSize = '90%';
break;
default:
fontSize = '80%';
}
}
if (anrConfig.dropdownFontSize) {
select2FontSize = anrConfig.dropdownFontSize;
} else {
switch(mw.config.get('skin')) {
case 'vector':
case 'vector-2022':
case 'minerva':
select2FontSize = '0.9em';
break;
case 'monobook':
select2FontSize = '1.03em';
break;
case 'timeless':
select2FontSize = '0.94em';
break;
default:
select2FontSize = '0.9em';
}
}
 
var styleAppended = false;
341 ⟶ 349行目:
'modal': true,
'open': initializeAnrDialog,
'buttons': [{mainDialogButtons
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
 
359 ⟶ 361行目:
 
getSectionsS(); // Get sections on WP:AN/S
dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, fontSize); // Initialize the design of the dialog
getVipList(); // Show VIP list
getPredefinedReasons(); // Show the select box for predefined reasons
414 ⟶ 416行目:
async function getSectionsS() {
const $label = $('#anr-target-options-label'); // Label of '報告先'
$label.append(dragoLib.toggleLoadingSpinner('add')); // Show a loading spinner while trying to get sections on WP:AN/S
 
const parseparsed = await dragoLib.parsePage(ANS, 'sections');
if (parseparsed) {
 
// Get VIP's names
const sectionInfo = parseparsed.sections;
const excludeList = [
'系列が立てられていないもの',
445 ⟶ 447行目:
const sectionList = [];
for (let i = 0; i < sectionInfo.length; i++) {
if (!isInArray$.inArray(sectionInfo[i].line, excludeList) === -1 && sectionInfo[i].index.indexOf('T') === -1) {
sectionList.push(`<option>${sectionInfo[i].line}</option>`);
}
455 ⟶ 457行目:
alert('WP:AN/Sのセクションリストを取得できませんでした。ダイアログを開き直すと改善する場合があります。');
}
dragoLib.toggleLoadingSpinner('remove');
 
}
 
/**
* Function to add/remove/move a loading spinner
* @param {string} action 'add', 'remove', or 'move'
*/
function toggleLoadingSpinner(action) {
const img = '<img class="anr-loading-spinner" src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" ' +
'style="vertical-align: middle; max-height: 100%; border: 0;">';
switch(action) {
case 'add':
return img;
case 'remove':
$('.anr-loading-spinner').remove();
return '';
case 'move':
$('.anr-loading-spinner').remove();
return img;
}
}
 
/**
* Function to get a pages's information
* @param {string} pagename
* @param {string} prop wikitext, sections, wikitext|sections
* @param {number} sectionNum optional
* @returns {Promise} res.parse ({{sections: []}, wikitext: ''}) (undefined if query failed)
*/
function parsePage(pagename, prop, sectionNum) {
return new Promise(function(resolve) {
var params = {
'action': 'parse',
'page': pagename,
'prop': prop,
'formatversion': 2
}
if (sectionNum) params = Object.assign(params, {'section': sectionNum}); // Concatenate params
new mw.Api().get(params).then(function(res){
resolve(res && res.parse ? res.parse : undefined);
});
});
}
 
// Function to change the CSS of the dialog
function dialogCSS() {
$('.ui-dialog-content, .ui-dialog-buttonpane, .ui-corner-all, .ui-draggable, .ui-resizable').css('background', anrConfig.backgroundColor);
$('.ui-button').css({
'color': 'black',
'background-color': 'white'
});
$('.ui-dialog-titlebar, .ui-dialog-titlebar-close').attr('style', `background: ${anrConfig.headerColor} !important;`);
$('.ui-dialog').css('font-size', fontSize);
}
 
// WP:VIP list (for copy to clipboard)
async function getVipList() {
 
const parseparsed = await dragoLib.parsePage(VIP, 'sections');
if (parseparsed) {
 
// Get VIP's names
const sectionInfo = parseparsed.sections;
const excludeList = [
'記述について',
534 ⟶ 484行目:
const vipList = [];
for (let i = 0; i < sectionInfo.length; i++) {
if (!isInArray$.inArray(sectionInfo[i].line, excludeList) === -1 && sectionInfo[i].level == 3) {
vipList.push(`<option>${sectionInfo[i].line}</option>`);
}
547 ⟶ 497行目:
.children('optgroup').append(vipList.join(''));
$('#anr-viplist-div').css('display', 'block');
dragoLib.centerDialog('#anr-modal-dialog');
}
 
568 ⟶ 518行目:
}
$('#anr-predefinedreasons-div').css('display', 'block');
dragoLib.centerDialog('#anr-modal-dialog');
 
}
}
 
function centerDialog() {
var $dialog;
if ($('#anr-preview-dialog').length !== 0) {
$dialog = $('#anr-preview-dialog');
} else if ($('#anr-drpreview-dialog').length !== 0) {
$dialog = $('#anr-drpreview-dialog');
} else {
$dialog = $('#anr-modal-dialog');
}
$dialog.dialog({'position': {my: 'center', at: 'center', of: window}});
}
 
594 ⟶ 532行目:
const inputID = '#' + $(this).attr('id');
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type
const inputVal = dragoLib.trim2($(this).val().trimANR(); // Username
if (!inputVal) return;
 
var username, logid;
if (type === 'logid' && (username = dragoLib.getKeyByValue(Logids, logid = inputVal))) { // if t=logid and the logid can be converted to a username
 
// If either of the username or the logid is already in the array 'users' and if they have yet to be listed as duplicates
if ((isInArray$.inArray(username, users) !== -1 || isInArray$.inArray(logid, users)) !== -1 && !isInArray$.inArray(username, duplicates) === -1 && !isInArray$.inArray(logid, duplicates) === -1) {
duplicates.push(username, logid); // List both the username and the logid as duplicates
}
608 ⟶ 546行目:
 
// If the username is already in the array 'users' (and if it hasn't been listed as a duplicate)
if (isInArray$.inArray(username = inputVal, users) !== -1 && !isInArray$.inArray(username, duplicates) === -1) {
duplicates.push(username); // List the username as a duplicate
}
629 ⟶ 567行目:
// Update the target section for cases in which the date has changed since the date-dependent section was chosen
if (sectionToEdit.match(/^\d{4}年\d{1,2}月\d{1,2}日 - \d{1,2}日新規報告$/)) {
const sectionIDate = getSectionIdragoLib.getSection5('報告', false);
sectionToEdit = sectionIDate;
$('#anr-section-i-options-date').text(sectionIDate);
668 ⟶ 606行目:
 
}
 
// The reason of the report
var fixedReason = $('#anr-predefinedreasons-select').find('option').filter(':selected').text();
fixedReason = fixedReason === '定型文を使用する場合は選択してください' ? '': fixedReason;
var reason = fixedReason + dragoLib.trim2($('#anr-reason-text').val().trimANR();
 
// Check if necessary fields are filled
695 ⟶ 633行目:
 
// Get edit summary
const summaryText = dragoLib.trim2($('#anr-summary-text').val().trimANR(), editSummarySection = '/*' + sectionToEdit + '*/';
var editSummary, summaryCustomized;
if (summaryText) {
703 ⟶ 641行目:
editSummary = editSummarySection + genEditSummary().replace(' - ', '') + scriptAd;
}
 
// Warn if a username is hidden but shown in the summary
if (summaryCustomized) {
709 ⟶ 647行目:
for (let i = 0; i < types.length; i++) {
let type = types[i], inputVal = users[i], username;
if (type === 'logid' && (username = dragoLib.getKeyByValue(Logids, inputVal)) && editSummary.indexOf(username) !== -1) hiddenUsernames.push(username);
}
if (hiddenUsernames.length !== 0) {
722 ⟶ 660行目:
const UserAN = '{{UserAN|t=TYPE|USER}}';
if (users.length < 2) { // If user to report is just one
reportText = '\* ' + UserANdragoLib.replaceAllANRreplaceAll2(UserAN, 'TYPE', types[0], 'USER', users[0]) + ' - ' + reason;
} else { // If two or more
for (let i = 0; i < users.length; i++) {
reportText += '\* ' + UserANdragoLib.replaceAllANRreplaceAll2(UserAN, 'TYPE', types[i], 'USER', users[i]) + '\n';
}
reportText += ': ' + reason;
756 ⟶ 694行目:
' <div id="anr-preview-header" style="padding: 0.5em;">' +
' <p id="anr-preview-loading">' +
` プレビューを読み込み中${dragoLib.toggleLoadingSpinner('add')}` +
' </p>' +
' <p id="anr-preview-warning" style="display: none;">' +
783 ⟶ 721行目:
 
// Initialize the design of the dialog
dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, fontSize);
 
// Convert text on the dialog to html
const parsed = await convertWikitextToHtmlFormatdragoLib.getParsedHtml(ep.reportText, ep.editSummary);
if (parsed) {
 
const previewHtml = parsed.htmltext;
const summaryHtml = parsed.htmlsummary.replaceAllANRreplace('/API'/g, ep.pageToEdit);
$('#anr-preview-text').append(previewHtml);
$('#anr-preview-summary').append(summaryHtml);
798 ⟶ 736行目:
$('#anr-preview-loading').remove();
$('#anr-preview-warning').css('display', 'inline');
dragoLib.centerDialog('#anr-preview-dialog');
 
} else {
$('#anr-preview-loading').text('プレビューの読み込みに失敗しました').css('color', 'MediumVioletRed');
dragoLib.centerDialog('#anr-preview-dialog');
setTimeout(function(){
$('#anr-preview-dialog').dialog('close');
817 ⟶ 755行目:
});
 
}
 
/**
* Function to convert wikitext to its HTML format
* @param {string} wikitext The wikitext to convert
* @param {string} wikisummary The summary to convert
* @returns {Promise<{htmltext: string, htmlsummary: string}>}
*/
function convertWikitextToHtmlFormat(wikitext, wikisummary) {
return new Promise(function(resolve) {
new mw.Api().post({
'action': 'parse',
'text': wikitext,
'summary': wikisummary,
'contentmodel': 'wikitext',
'prop': 'text',
'disableeditsection': true,
'formatversion': 2
}).then(function(res) {
resolve({
'htmltext': res.parse.text,
'htmlsummary': res.parse.parsedsummary
});
}).catch(function(code, err) {
console.log(err.error.info);
resolve();
});
});
}
 
863 ⟶ 773行目:
.find('form').css('display', 'none'); // Hide dialog content
 
// Add user pages to watchlist if the checkbox is checked
if ($('#anr-watchlist-checkbox').is(':checked')) {
const pagenames = [];
for (let i = 0; i < ep.types.length; i++) {
const type = ep.types[i], user = ep.users[i];
if (type === 'User2' || type === 'UNL' || type === 'IP2') {
if ($.inArray('利用者:' + user, pagenames) === -1) pagenames.push('利用者:' + user);
} else if (type === 'logid') {
let username;
if (username = dragoLib.getKeyByValue(Logids, user) && $.inArray('利用者:' + username, pagenames) === -1) pagenames.push('利用者:' + username);
}
}
dragoLib.watchPages(pagenames);
}
 
// Report
reportUsers(ep);
addUsersToWatchlist(ep);
 
}
 
const generateButtons = function(callback, ep) {
// Function to execute report
return [{
'text': '続行',
'click': function(){
$(this).dialog({'buttons': [] });
eval(callback); // ep is used in the callback
}
}, {
'text': '戻る',
'click': function(){
$(this).find('form').css('display', 'block');
$('.anr-editing').remove();
$(this).dialog({
'width': 'auto',
'buttons': mainDialogButtons
});
}
}, {
'text': '中止',
'click': function(){
$(this).dialog('close');
}
}];
};
 
async function reportUsers(ep) {
 
881 ⟶ 830行目:
// Update dialog buttons
$('#anr-modal-dialog').dialog({
'buttons': [{generateButtons('reportUsers2(ep)', ep)
'text': '続行',
'click': function(){
$(this).dialog({'buttons': [] });
reportUsers2(ep);
}
}, {
'text': '戻る',
'click': function(){
$(this).find('form').css('display', 'block');
$('.anr-editing').remove();
$(this).dialog({
'width': 'auto',
'buttons': [{
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
}
}, {
'text': '中止',
'click': function(){
$(this).dialog('close');
}
}]
});
 
939 ⟶ 861行目:
previewDuplicateReports(dr.wikitext, dr.dupUsernames);
}
}, {]
.concat(generateButtons('textreportUsers3(ep)':, '続行',ep))
'click': function(){
$(this).dialog({'buttons': [] });
reportUsers3(ep);
}
}, {
'text': '戻る',
'click': function(){
$(this).find('form').css('display', 'block');
$('.anr-editing').remove();
$(this).dialog({
'width': 'auto',
'buttons': [{
'text': 'プレビュー',
'click': preview
}, {
'text': '報告',
'click': report
}]
});
}
}, {
'text': '中止',
'click': function(){
$(this).dialog('close');
}
}]
});
 
}
 
}
 
async function reportUsers3(ep) {
 
// Get the latest revision
var msg = `<p>最新版を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
const ts = await dragoLib.getTimestamps(ep.pageToEdit);
if (!ts) {
queryFailed(ep);
return;
}
msg = '<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>セクション情報を取得しています${dragoLib.toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
 
// Get section number and content
const parsed = await dragoLib.parsePage(ep.pageToEdit, ep.sectionToEdit);
if (parsed) {
var sectionNum = parsed.sectionNumber, wikitext = parsed.wikitext[0], reportText;
 
if (!sectionNum) {
sectionNotFound(ep);
return;
}
 
if (ep.reportToANS) { // If the target is WP:AN/S
 
// Add div if the target section is 'その他' but lacks div for the current date
const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${dragoLib.today()}}}|div}}`;
if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText;
 
// Get the report text to submit
let sockInfo = dragoLib.findTemplates(wikitext, 'sockinfo'); // Array
if (sockInfo.length === 1) { // One section on WP:AN/S should have one SockInfo
sockInfo = sockInfo[0];
const sockInfoNoClosure = dragoLib.trim2(sockInfo.substring(0, sockInfo.length - 2));
reportText = wikitext.replace(sockInfo, sockInfoNoClosure + '\n\n' + ep.reportText + '\n\n}}');
} else { // There's a problem with SockInfo
msg = // Show error and quit the procedure
dragoLib.toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">取得に失敗しました</p>' +
`<p>報告先セクションに{{SockInfo}}がない、または複数個あるため報告場所を特定できませんでした</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
return;
}
 
} else { // If the target is WP:AN/I or WP:AN/3RR
reportText = dragoLib.trim2(wikitext) + '\n\n' + ep.reportText;
}
 
} else {
queryFailed(ep);
return;
}
msg = '<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>報告を試みています${dragoLib.toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
 
// Edit
const result = await dragoLib.editPage(ep.pageToEdit, reportText, 'text', ts.baseTS, ts.curTS, sectionNum, ep.editSummary, debugMode.causeIntentionalError ? '' : undefined);
dragoLib.toggleLoadingSpinner('remove');
switch(result) {
case true: // Edit succeeded
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
editDone(ep, false);
break;
case false: // Unknown error occurred
msg = '<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' + manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
break;
default: // Known error occurred
msg = '<p style="color: MediumVioletRed">報告に失敗しました</p>' +
`<p>詳細: ${result}</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
}
 
993 ⟶ 971行目:
 
// Update message on the dialog
var msg = `<p>報告対象者のブロック情報を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
 
1,004 ⟶ 982行目:
case 'User2':
case 'IP2':
if (!isInArray$.inArray(inputVal, usersForBlockCheck) === -1) usersForBlockCheck.push(inputVal);
break;
case 'logid':
let username;
if ((username = dragoLib.getKeyByValue(Logids, inputVal)) !== undefined) { // If the logid can be converted to a username
if (!isInArray$.inArray(username, usersForBlockCheck) === -1) usersForBlockCheck.push(username);
}
break;
1,017 ⟶ 995行目:
 
// Check if any of the users is blocked
const blocked = await getBlockeddragoLib.getRestricted(usersForBlockCheck);
 
// If any of the users is blocked
1,024 ⟶ 1,002行目:
// Update message on the dialog
msg =
dragoLib.toggleLoadingSpinner('remove') +
`<p style="color: MediumVioletRed">ブロック済みの利用者を検出しました</p>`;
$('.anr-editing').append(msg);
1,031 ⟶ 1,009行目:
$('#anr-user-div :text').each(function() { // Loop through all inputs
const inputID = '#' + $(this).attr('id');
const inputVal = dragoLib.trim2($(inputID).val().trimANR();
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div'));
const $bsLink = $(inputID.replace('input', 'blockstatus'));
 
$bsLinkDiv.css('display', 'none'); // Temporarily hide the div
if (isInArray$.inArray(inputVal, blocked) !== -1) {
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + inputVal));
$bsLinkDiv.css('display', 'block');
1,046 ⟶ 1,024行目:
// Update message on the dialog
msg =
dragoLib.toggleLoadingSpinner('remove') +
`<p style="color: MediumSeaGreen">ブロック済みの利用者は検出されませんでした</p>`;
$('.anr-editing').append(msg);
1,064 ⟶ 1,042行目:
 
// Update message on the dialog
var msg = `<p>重複報告情報を取得しています${dragoLib.toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
 
// Get sections and the whole wikitext of the page to which to report
const parsed = await parsePage(ep.pageToEdit, 'wikitext|sections');
const sections = parsed.sections, wikitext = parsed.wikitext;
const sectiontitles = [];
const sectionheaders = ['']; // Array of equal-enclosed section headers (e.g. == SECTION ==) (Note: the top section has no header, thus arr[0] = '')
 
// Get section titles and their corresponding equal-enclosed wikitext
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
if (section.index.indexOf('T') === -1) { // If the section isn't a transcluded one
sectiontitles.push(section.line); // Get the title of the section
if (section.level == 2) { // Get equal-enclosed section headers
sectionheaders.push('== ' + section.line + ' ==');
} else if (section.level == 3) {
sectionheaders.push('=== ' + section.line + ' ===');
} else if (section.level == 4) {
sectionheaders.push('==== ' + section.line + ' ====');
} else if (section.level == 5) {
sectionheaders.push('===== ' + section.line + ' =====');
}
}
}
 
// The sections in which to search for duplicate reports
const tarSectionsI = [
getSectionIdragoLib.getSection5('報告', true),
getSectionIdragoLib.getSection5('報告', false),
'不適切な利用者名',
'公開アカウント',
1,132 ⟶ 1,087行目:
default: // Error: Target pagename not defined
msg =
dragoLib.toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">致命的なエラーが発生しました</p><br>' +
`<p>${developerLink}に、<u>${ep.wikiPagename}</u>への報告においてこのエラーが発生したことの報告をお願いします。</p>` +
manualEdit(ep);
1,141 ⟶ 1,096行目:
}
 
// ErrorGet handlersections forand whenthe pageToEditwhole doesn'twikitext haveof sectionsthe thatpage it'sto supposedwhich to havereport
const parsed = await dragoLib.parsePage(ep.pageToEdit, tarSections);
if (!arrayIsInArray(tarSections, sectiontitles)) {
if (parsed) {
sectionNotFound(ep);
// Error handler for when pageToEdit doesn't have sections that it's supposed to have
if (parsed.wikitext.length !== tarSections.length) {
sectionNotFound(ep);
return {'wikitext': null};
}
} else { // API query failed
return {'wikitext': null};
}
 
// Separate the content of the parsed page into the content of each section
var sectionsPiped = sectiontitles.join('|').escapeRegExpANR();
var regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
var sectionContent = wikitext.split(regExp); // Array of the content of each section, without section headers
for (let i = 0; i < sectionContent.length; i++) { // Re-add to sectionContent the '== TITLE ==' header that's been removed by the split() above
sectionContent[i] = sectionheaders[i] + sectionContent[i];
}
 
// Remove the contents of irrelevant sections from the array 'sectionContent'
sectionsPiped = tarSections.join('|').escapeRegExpANR();
regExp = new RegExp(`={2,5}\\s*(?:${sectionsPiped})\\s*={2,5}`, 'g');
for (let i = sectionContent.length -1; i >= 0; i--) {
if (sectionContent[i].search(regExp) === -1) sectionContent.splice(i, 1);
}
 
1,188 ⟶ 1,134行目:
break;
case 'logid': // The corresponding username needs to be checked
if (username = dragoLib.getKeyByValue(Logids, logid = usersDR[i])) usersDR.push(username);
break;
default: // t=diff or t=none: no need to do anything because the relevant input value is already in the array
1,196 ⟶ 1,142行目:
// Extract UserAN templates and find duplicate reports
const dupTemplates = [], dupUsernames = [];
const stringContainsElementInArray = function(str, arr) {
for (let i = sectionContent.length -1; i >= 0; i--) { // Loop through all section contents
for (let i = 0; i < arr.length; i++) {
if (str.indexOf(arr[i]) !== -1) {
return arr[i];
}
}
};
for (let i = parsed.wikitext.length -1; i >= 0; i--) { // Loop through all section contents
 
const templates = dragoLib.findTemplates(sectionContentparsed.wikitext[i], 'useran'); // Extract UserAN templates as an array
let dupUsername, duplicateFound;
 
1,204 ⟶ 1,157行目:
for (let j = templates.length -1; j >= 0; j--) { // Loop through all the occurences of UserAN
if (dupUsername = stringContainsElementInArray(templates[j], usersDR)) { // If there's a duplciate report
if (!isInArray$.inArray(templates[j], dupTemplates) === -1) dupTemplates.push(templates[j]); // List the UserAN as a duplicate
if (!isInArray$.inArray(dupUsername, dupUsernames) === -1) dupUsernames.push(dupUsername); // List the duplicate username
duplicateFound = true;
}
}
}
if (!duplicateFound) sectionContentparsed.wikitext.splice(i, 1); // Remove the section text from the array if it doesn't involve duplicate reports
 
}
 
// Return text and update dialog
if (sectionContentparsed.wikitext.length === 0) { // If there's no duplicate report
 
msg = `<p style="color: MediumSeaGreen">重複報告は検出されませんでした${dragoLib.toggleLoadingSpinner('remove')}</p>`;
$('.anr-editing').append(msg); // Update message on the dialog
return; // Return undefined
1,223 ⟶ 1,176行目:
} else { // If there're duplicate reports
 
msg = `<p style="color: MediumVioletRed">重複報告の可能性があります${dragoLib.toggleLoadingSpinner('remove')}</p>`;
$('.anr-editing').append(msg);
 
// Highlight all the duplciate UserAN occurences
sectionContentvar wikitext = sectionContentparsed.wikitext.join(''); // Merge the separate sections
for (let i = 0; i < dupTemplates.length; i++) {
sectionContentwikitext = sectionContentdragoLib.replaceAllANRreplaceAll2(wikitext, dupTemplates[i], `<span style="background-color: ${anrConfig.headerColor}">${dupTemplates[i]}</span>`);
}
 
// Return wikitext to fetch preview from
return {
'wikitext': sectionContentwikitext,
'dupUsernames': dupUsernames
};
1,245 ⟶ 1,198行目:
function sectionNotFound(ep) {
const msg =
dragoLib.toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">取得に失敗しました</p>' +
'<p>指定されたセクションが見つかりませんでした</p>' +
1,263 ⟶ 1,216行目:
'<br>' +
'<p>手動編集用:</p>' +
`<textarea disabled class="anr-dialog-textarea" rows="43">${ep.reportText}</textarea>` +
'<br>' +
'<p>要約:</p>' +
`<textarea disabled class="anr-dialog-textarea" rows="2">${ep.editSummary.replace(scriptAd, '')}</textarea>`;
return meHtml;
1,314 ⟶ 1,265行目:
// Show the button(s) on the dialog
$dialog.dialog({'buttons': btns});
if (editFailed) dragoLib.centerDialog('#anr-modal-dialog');
 
}
1,329 ⟶ 1,280行目:
for (let i = 0; i < dupUsernames.length; i++) {
let username, logid;
if (username = dragoLib.getKeyByValue(Logids, logid = dupUsernames[i])) { // if the dupUsername is a logid and that can be converted to a username
usernames.push(`${username} (${logid})`);
} else if (logid = Logids[username = dupUsernames[i]]) { // if the dupUsername is a username and that can be converted to a logid
1,343 ⟶ 1,294行目:
' <div id="anr-drpreview-header" style="padding: 0.5em;">' +
' <p id="anr-drpreview-loading">' +
` 読み込み中${dragoLib.toggleLoadingSpinner('add')}` +
' </p>' +
' <p id="anr-drpreview-userlist" style="display: none; font-size: larger">' +
1,365 ⟶ 1,316行目:
 
// Initialize the design of the dialog
dragoLib.dialogCSS(anrConfig.headerColor, anrConfig.backgroundColor, fontSize);
 
// Convert the wikitext to an html form
const wikitextInHtml = await convertWikitextToHtmlFormatdragoLib.getParsedHtml(wikitext.trim(), '');
if (wikitextInHtml) {
$('#anr-drpreview-body').append(wikitextInHtml.htmltext);
1,375 ⟶ 1,326行目:
$('#anr-drpreview-loading').remove();
$('#anr-drpreview-userlist').css('display', 'inline');
dragoLib.centerDialog('#anr-drpreview-dialog');
} else {
$('#anr-drpreview-loading').text('読み込みに失敗しました').css('color', 'MediumVioletRed');
dragoLib.centerDialog('#anr-drpreview-dialog');
setTimeout(function(){
$('#anr-drpreview-dialog').dialog('close');
1,393 ⟶ 1,344行目:
});
 
}
 
async function reportUsers3(ep) {
 
const ts = await getTimestamps(ep);
if (!ts) return;
const baseTS = ts.baseTS, curTS = ts.curTS;
 
const sectionNum = await getSectionNumber(ep);
if (!sectionNum) return;
 
const reportText = await getReportText(ep, sectionNum);
if (!reportText) return;
 
edit(ep, sectionNum, reportText, baseTS, curTS);
 
}
 
// Function to get the latest revision of the administrator's noticeboard
function getTimestamps(ep) {
return new Promise(function(resolve) {
 
var msg = `<p>最新版を取得しています${toggleLoadingSpinner('add')}</p>`;
$('.anr-editing').append(msg);
 
new mw.Api().get({
'action': 'query',
'titles': ep.pageToEdit,
'prop': 'revisions',
'curtimestamp': true,
'formatversion': 2
}).then(function(res){
 
var resPages;
if (res && res.query && (resPages = res.query.pages)) { // If the latest revision is successfully retrieved
if (!resPages[0].missing) { // .missing is true if the page doesn't exist, otherwise undefined
 
// Get the timestamps of the latest revision and the API query
const baseTS = resPages[0].revisions[0].timestamp; // The TS of the latest revision
const curTS = res.curtimestamp; // The TS of the API query
 
// Update message on the dialog
msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>セクション番号を取得しています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
 
// Return the timestamps as an object
resolve({
'baseTS': baseTS,
'curTS': curTS
});
 
} else { // If the page doesn't exist
 
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">エラー: 報告先のページが存在しません</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
resolve();
 
}
 
} else { // If revision retrieval fails
queryFailed(ep);
resolve();
}
 
});
 
});
}
 
function addUsersToWatchlist(ep) {
if (!$('#anr-watchlist-checkbox').is(':checked')) return console.log('ウォッチリストへの追加設定はオフになっています。');
return new Promise(function(resolve) {
 
// Get pagenames to watch
const pagenames = [];
for (let i = 0; i < ep.types.length; i++) {
const type = ep.types[i], user = ep.users[i];
if (type === 'User2' || type === 'UNL' || type === 'IP2') {
if (!isInArray('利用者:' + user, pagenames)) pagenames.push('利用者:' + user);
} else if (type === 'logid') {
let username;
if (username = getKeyByValue(Logids, user) && !isInArray('利用者:' + username, pagenames)) pagenames.push('利用者:' + username);
}
}
 
// Add the pages to watchlist
new mw.Api().get({
'action': 'query',
'meta': 'tokens',
'type': 'watch'
}).then(function(res){
 
const token = res.query.tokens.watchtoken;
if (!token) {
resolve(mw.log.error('ウォッチトークンの取得に失敗しました'));
} else {
new mw.Api().post({
'action': 'watch',
'titles': pagenames.join('|'),
'token': token,
'formatversion': 2
}).then(function(res) {
resolve(console.log('以下のページをウォッチリストに追加しました:\n' + pagenames.join(', ')));
}).catch(function(code, err) {
resolve(mw.log.error('ウォッチリストへの追加に失敗しました:\n' + err.error.info));
});
}
 
});
 
});
}
 
1,515 ⟶ 1,349行目:
function queryFailed(ep) {
const msg =
dragoLib.toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">取得に失敗しました</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
}
 
// Function to get the section number from the section title
async function getSectionNumber(ep) {
 
const parse = await parsePage(ep.pageToEdit, 'sections');
var resSect, sectionNum;
if (parse && (resSect = parse.sections)) { // If the section list is successfully retrieved
 
// Get the titles of all sections and their section numbers
for (let i = 0; i < resSect.length; i++) {
if (resSect[i].line === ep.sectionToEdit) {
sectionNum = resSect[i].index;
break;
}
}
 
// Return a section number if the section is found, undefined if not
if (!sectionNum) {
sectionNotFound(ep);
return;
} else {
const msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>最新版のテキストを取得しています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
return sectionNum;
}
 
} else { // If the section list retrieval fails
queryFailed(ep);
return;
}
 
}
 
// Function to get the text to replace with the current text in the section
async function getReportText(ep, sectionNum) {
 
const parse = await parsePage(ep.pageToEdit, 'wikitext', sectionNum);
if (parse) {
 
// Update message
var msg =
'<p style="color: MediumSeaGreen">取得に成功しました</p>' +
`<p>報告を試みています${toggleLoadingSpinner('move')}</p>`;
$('.anr-editing').append(msg);
 
// Get the whole text to append
const wikitext = parse.wikitext;
var reportText;
if (ep.reportToANS) { // If the target is WP:AN/S
 
// Add div if the target section is 'その他' but lacks div for the current date
const miscHeader = `{{bgcolor|#eee|{{Visible anchor|他${today()}}}|div}}`;
if (ep.sectionToEdit === 'その他' && wikitext.indexOf(miscHeader) === -1) ep.reportText = '; ' + miscHeader + '\n\n' + ep.reportText;
 
// Get the report text to submit
let sockInfo = findTemplates(wikitext, 'sockinfo'); // Array
if (sockInfo.length === 1) { // One section on WP:AN/S should have one SockInfo
sockInfo = sockInfo[0];
const sockInfoNoClosure = sockInfo.substring(0, sockInfo.length - 2).trimANR();
reportText = wikitext.replace(sockInfo, sockInfoNoClosure + '\n\n' + ep.reportText + '\n\n}}');
return reportText;
} else { // There's a problem with SockInfo
msg = // Show error and quit the procedure
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' +
`<p>{{SockInfo}}がない、または複数個あるため報告場所を特定できませんでした</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
return;
}
 
} else { // If the target is WP:AN/I or WP:AN/3RR
reportText = wikitext.trimANR() + '\n\n' + ep.reportText;
return reportText;
}
 
} else { // If wikitext retrieval fails
queryFailed(ep);
return;
}
 
}
 
// Function to edit the page
function edit(ep, sectionNum, reportText, baseTS, curTS) {
 
new mw.Api().post({
'action': 'edit',
'title': ep.pageToEdit,
'section': sectionNum,
'text': reportText,
'summary': ep.editSummary,
'basetimestamp': baseTS,
'starttimestamp': curTS,
'token': debugMode.causeIntentionalError ? '': mw.user.tokens.get('csrfToken'),
'format': 'json'
}).then(function(res) {
 
toggleLoadingSpinner('remove');
$('.anr-editing').append(`<p style="color: MediumSeaGreen">報告が完了しました</p>`);
editDone(ep, false);
 
}).catch(function(code, err) {
var msg;
if (err && err.error) {
 
// Show the details of the error
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">報告に失敗しました</p><br>' +
'<p>詳細:</p>' +
`<p>${err.error.info}</p>` +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
 
} else { // If unknown error occurred
 
msg =
toggleLoadingSpinner('remove') +
'<p style="color: MediumVioletRed">不明なエラーが発生しました</p>' +
manualEdit(ep);
$('.anr-editing').append(msg);
editDone(ep, true);
 
}
});
 
}
 
/**
* Function to extract templates from wikitext
* @param {string} text The text in which to search for templates
* @param {string} templateName [Optional] Specify the template name (case-insensitive)
* @returns {Array} An array of the extracted templates
*/
function findTemplates(text, templateName) {
 
// Split the text with '{{', the head delimiter of templates
const tempInnerContent = text.split('{{'); // Note: tempInnerContent[0] is always an empty string or a string that has nothing to do with templates
const templates = []; // The array of extracted templates to return
 
// Extract templates from the text
if (tempInnerContent.length === 0) { // If the text has no tempalte in it
 
return templates; // Return an empty array
 
} else { // If the text has some templates in it
 
const nest = []; // Stores the element number of tempInnerContent if the element involves nested templates
for (let i = 1; i < tempInnerContent.length; i++) { // Loop through all elements in tempInnerContent (except tempInnerContent[0])
 
let tempTailCnt = (tempInnerContent[i].match(/\}\}/g) || []).length; // The number of '}}' in the split segment
let temp = ''; // Temporary escape hatch for templates
 
if (tempTailCnt === 0) { // The split segment not having any '}}' means that it nests another template
 
nest.push(i); // Push the element number into the array
 
} else if (tempTailCnt === 1) { // The split segment itself is the whole inner content of one template
 
temp = '{{' + tempInnerContent[i].split('}}')[0] + '}}';
if (!isInArray(temp, templates)) templates.push(temp);
 
} else if (tempTailCnt > 1) { // The split segment is part of more than one template (e.g. TL2|...}}...}} )
 
for (let j = 0; j < tempTailCnt; j++) { // Loop through all the nests
 
if (j === 0) { // The innermost template
 
temp = '{{' + tempInnerContent[i].split('}}')[j] + '}}'; // Same as when tempTailCnt === 1
if (!isInArray(temp, templates)) templates.push(temp);
 
} else { // Nesting templates
 
const elNum = nest[nest.length -1]; // The start of the nesting template
nest.pop();
const nestedTempInnerContent = tempInnerContent[i].split('}}');
 
temp = '{{' + tempInnerContent.slice(elNum, i).join('{{') + '{{' + nestedTempInnerContent.slice(0, j + 1).join('}}') + '}}';
if (!isInArray(temp, templates)) templates.push(temp);
 
}
 
}
 
}
 
}
 
// Check if the optional parameter is specified
if (templateName && templates.length !== 0) {
const templateRegExp = new RegExp(templateName, 'i');
for (let i = templates.length -1; i >= 0; i--) {
// Remove the template from the array if it's not an instance of the specified template
if (templates[i].split('|')[0].search(templateRegExp) === -1) templates.splice(i, 1);
}
}
 
return templates;
}
}
 
1,734 ⟶ 1,363行目:
const inputID = '#' + $(this).attr('id');
const type = $(inputID.replace('input', 'select')).children('option').filter(':selected').text(); // UserAN type specified in the dropdown
const reportee = dragoLib.trim2($(this).val().trimANR(); // Username
 
let link;
1,753 ⟶ 1,382行目:
link = reportee;
}
if (!isInArray$.inArray(link, links) === -1) links.push(link); // Push the link into the array
}
});
1,773 ⟶ 1,402行目:
return summary;
 
}
 
/**
* Function to get the current date and the section name on WP:AN/I to which to report users
* @param {boolean} last if true, returns the name of the last section
* @returns {string} section name
*/
function getSectionI(last){
 
const d = new Date();
if (last) {
let subtract = 5;
if (d.getDate() === 1 || d.getDate() === 2) {
subtract = 3;
} else if (d.getDate() === 31) {
subtract = 6;
}
d.setDate(d.getDate() - subtract);
}
 
var sectionName;
switch(true) {
case (1 <= d.getDate() && d.getDate() <= 5):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月1日 - 5日新規報告`;
break;
case (6 <= d.getDate() && d.getDate() <= 10):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月6日 - 10日新規報告`;
break;
case (11 <= d.getDate() && d.getDate() <= 15):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月11日 - 15日新規報告`;
break;
case (16 <= d.getDate() && d.getDate() <= 20):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月16日 - 20日新規報告`;
break;
case (21 <= d.getDate() && d.getDate() <= 25):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月21日 - 25日新規報告`;
break;
case (26 <= d.getDate() && d.getDate() <= lastDay(d.getFullYear(), d.getMonth())):
sectionName = `${d.getFullYear()}年${d.getMonth() + 1}月26日 - ${lastDay(d.getFullYear(), d.getMonth())}日新規報告`;
break;
default:
}
return sectionName;
 
}
 
// Function to check if a user exists locally
function userExists(username) {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'query',
'list': 'users',
'ususers': username,
'formatversion': 2
}).then(function(res){
resolve(res.query.users[0].userid !== undefined); // True if the user exists and false if not
});
});
}
 
1,837 ⟶ 1,408行目:
function updateTypeDropdown(inputID) {
 
const tarVal = dragoLib.trim2($(inputID).val().trimANR(); // The value typed into the input
const selectID = inputID.replace('input', 'select'); // #anr-userX-select
const checkboxDivID = inputID.replace('input', 'checkbox-div'); // #anr-userX-checkbox-div
1,870 ⟶ 1,441行目:
toggleBlockStatusLink(inputID, false, false);
 
} else if (await dragoLib.userExists(tarVal)) { // if user
 
$(selectID).prop('disabled', false); // enable dropdown (repeated to prevent a strange lag)
1,912 ⟶ 1,483行目:
function toggleBlockStatusLink(inputID, forceHide, convertLogid) {
 
dragoLib.centerDialog('#anr-modal-dialog');
 
const inputVal = dragoLib.trim2($(inputID).val().trimANR(); // The value in the input
const $bsLinkDiv = $(inputID.replace('input', 'blockstatus-div')); // #anr-userX-blockstatus-div
const $bsLink = $(inputID.replace('input', 'blockstatus')); // #anr-userX-blockstatus
1,921 ⟶ 1,492行目:
if (forceHide && convertLogid) { // t=logid
// Check if the logid can be converted to a username and if it can, proceed to block check, and if it can't, just hide the block status link
if (!(username = dragoLib.getKeyByValue(Logids, logid = inputVal))) {
$bsLinkDiv.css('display', 'none');
dragoLib.centerDialog('#anr-modal-dialog');
return;
}
} else if (forceHide) { // t=diff or t=none
$bsLinkDiv.css('display', 'none'); // Hide the link div
dragoLib.centerDialog('#anr-modal-dialog');
return;
} else { // t=UNL, t=User2, or t=IP2
1,935 ⟶ 1,506行目:
 
// Check the block status of the user, and if blocked, update the bsLink, or else, hide the link
getBlockeddragoLib.getRestricted([username]).then(function(blocked) {
if (blocked.length !== 0) { // If the user is blocked
$bsLink.attr('href', mw.util.getUrl('特別:投稿記録/' + username)); // Update the link
1,942 ⟶ 1,513行目:
$bsLinkDiv.css('display', 'none'); // Hide the link div
}
dragoLib.centerDialog('#anr-modal-dialog');
});
 
}
 
// Function to get an array of blocked users & IPs from an array of random users & IPs
async function getBlocked(namesArr) {
 
const users = [], ips = [];
var blocked = [];
 
// Sort names to users and IPs
for (let i = 0; i < namesArr.length; i++) {
if (mw.util.isIPAddress(namesArr[i], true)) { // Push IPs into the array
ips.push(namesArr[i]);
} else { // Push users into the array
users.push(namesArr[i]);
}
}
 
// Check who's (b)locked
if (users.length !== 0 && ips.length !== 0) { // If namesArr contains both users and IPs
 
// Check local block status
blocked = blocked.concat(
await getBlockedUsers(users),
await getBlockedIps(ips)
);
 
// Remove users/IPs that are already in the array 'blocked' (to make the code faster)
for (let i = users.length -1; i >= 0; i--) {
if (isInArray(users[i], blocked)) {
users.splice(i ,1);
}
}
for (let i = ips.length -1; i >= 0; i--) {
if (isInArray(ips[i], blocked)) {
ips.splice(i ,1);
}
}
 
// Check global (b)lock status
blocked = blocked.concat(
await getGloballyLockedUsers(users),
await getGloballyBlockedIps(ips)
);
 
} else if (users.length !== 0) { // If namesArr only contains users
 
// Check local block status
blocked = blocked.concat(await getBlockedUsers(users));
 
// Remove users that are already in the array 'blocked' (to make the code faster)
for (let i = users.length -1; i >= 0; i--) {
if (isInArray(users[i], blocked)) {
users.splice(i ,1);
}
}
 
// Check global lock status
blocked = blocked.concat(await getGloballyLockedUsers(users));
 
} else if (ips.length !== 0) { // If namesArr only contains IPs
 
// Check local block status
blocked = blocked.concat(await getBlockedIps(ips));
 
// Remove IPs that are already in the array 'blocked' (to make the code faster)
for (let i = ips.length -1; i >= 0; i--) {
if (isInArray(ips[i], blocked)) {
ips.splice(i ,1);
}
}
 
// Check global block status
blocked = blocked.concat(await getGloballyBlockedIps(ips));
 
} else {
// Do nothing
}
 
return blocked;
 
}
 
/**
* Function to get an array of blocked users from an array of random users
* @param {Array} usersArr
* @returns {Promise<Array>}
*/
function getBlockedUsers(usersArr) { // Note: this function needs to be modified if there're cases in which the reportees are more than 50
if (usersArr.length === 0) return [];
return new Promise(function(resolve) {
new mw.Api().post({
'action': 'query',
'list': 'blocks',
'bklimit': usersArr.length,
'bkusers': usersArr.join('|'),
'bkprop': 'user',
'formatversion': 2
}).then(function(res){
const resBlk = res.query.blocks, blockedUsers = [];
if (resBlk.length !== 0) { // If someone is blocked
for (let i = 0; i < resBlk.length; i++) {
blockedUsers.push(resBlk[i].user); // Push blocked users into the array
}
}
resolve(blockedUsers); // Return e.g. [user1, user2...], or an empty array
});
});
}
 
/**
* Function to get an array of locked users from an array of random users
* @param {Array} usersArr
* @returns {Promise<Array>}
*/
async function getGloballyLockedUsers(usersArr) {
if (usersArr.length === 0) return [];
const lockedUsers = [];
for (let i = 0; i < usersArr.length; i++) {
const locked = await userIsLocked(usersArr[i]);
if (locked) lockedUsers.push(usersArr[i]);
}
return lockedUsers;
}
 
/**
* Function to check if a user is globally locked
* @param {string} user
* @returns {Promise<boolean>}
*/
function userIsLocked(user) {
return new Promise(function(resolve) {
new mw.Api().get({
action: 'query',
list: 'globalallusers',
agulimit: 1,
agufrom: user,
aguto: user,
aguprop: 'lockinfo'
}).then(function(res) {
const resLck = res.query.globalallusers;
resolve(resLck.length === 0 ? false : resLck[0].locked !== undefined); // resLck[0].locked === '' if locked, otherwise undefined
});
});
}
 
/**
* Function to get an array of locally blocked IPs from an array of random IPs
* @param {Array} ipsArr
* @returns {Promise<Array>}
*/
async function getBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
const blockedIps = [];
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsBlocked(ipsArr[i]);
if (blocked) blockedIps.push(ipsArr[i]);
}
return blockedIps;
}
 
/**
* Function to check if a given IP is locally blocked
* @param {String} ip
* @returns {Promise<boolean>}
*/
function ipIsBlocked(ip) {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'query',
'list': 'blocks',
'bklimit': 1,
'bkip': ip,
'bkprop': 'user',
'formatversion': 2
}).then(function(res){
resolve(res.query.blocks.length !== 0);
});
});
}
 
/**
* Function to get an array of globally blocked IPs from an array of random IPs
* @param {Array} ipsArr
* @returns {Promise<Array>}
*/
async function getGloballyBlockedIps(ipsArr) {
if (ipsArr.length === 0) return [];
const blockedIps = [];
for (let i = 0; i < ipsArr.length; i++) {
const blocked = await ipIsGloballyBlocked(ipsArr[i]);
if (blocked) blockedIps.push(ipsArr[i]);
}
return blockedIps;
}
 
/**
* Function to check if a given IP is globally blocked
* @param {String} ip
* @returns {Promise<boolean>}
*/
function ipIsGloballyBlocked(ip) {
return new Promise(function(resolve) {
new mw.Api().get({
'action': 'query',
'list': 'globalblocks',
'bgip': ip,
'bglimit': 1,
'bgprop': 'address'
}).then(function(res){
resolve(res.query.globalblocks.length !== 0);
});
});
}
 
2,182 ⟶ 1,541行目:
$(document).off('change', '#anr-viplist-select').on('change', '#anr-viplist-select', function() {
const vipSelectVal = $('#anr-viplist-select').find('option').filter(':selected').text().trim();
dragoLib.copyToClipboard('[[WP:VIP#' + vipSelectVal + ']]');
});
 
2,201 ⟶ 1,560行目:
$('#anr-section-i-div').css('display', 'block');
$('#anr-section-s-div').css('display', 'none');
$('#anr-section-i-options-date').text(getSectionIdragoLib.getSection5('報告', false));
$('#anr-section-i-select').css({'width': $(this).innerWidth()});
$('#anr-target-pagelink-div').css('display', 'block');
2,220 ⟶ 1,579行目:
break;
}
dragoLib.centerDialog('#anr-modal-dialog');
});
 
2,246 ⟶ 1,605行目:
const valSelected = $(selectID).children('option').filter(':selected').text(); // Selected type
const inputID = selectID.replace('select', 'input'); // #anr-userX-input
const valInput = dragoLib.trim2($(inputID).val().trimANR(); // The input value
const checkboxDivID = selectID.replace('select', 'checkbox-div'); // #anr-userX-checkbox-div
const checkboxID = selectID.replace('select', 'checkbox'); // #anr-userX-checkbox
2,293 ⟶ 1,652行目:
const selectID = checkboxID.replace('checkbox', 'select'); // #anr-userX-select
const inputID = checkboxID.replace('checkbox', 'input'); // #anr-userX-input
const inputVal = dragoLib.trim2($(inputID).val().trimANR();
const idlinkID = checkboxID.replace('checkbox', 'idlink'); // #anr-userX-idlink
const idlinkDivID = checkboxID.replace('checkbox', 'idlink-div'); // #anr-userX-idlink-div
2,332 ⟶ 1,691行目:
} else { // if the checkbox is unchecked (the input value is a logid and this needs to be converted to a username)
 
if (username = dragoLib.getKeyByValue(Logids, logid = inputVal)) { // Username converted from logid
$(inputID).val(username); // Replace the logid with the username in the object
$(selectID).children('.anr-opt-UNL').prop('hidden', false).prop('selected', true);
2,356 ⟶ 1,715行目:
$(document).off('click', '#anr-addBtn').on('click', '#anr-addBtn', function(){
userCnt++;
$('#anr-btn-div').before(userDivdragoLib.replaceAllANRreplaceAll2(userDiv, '1-', userCnt + '-'));
$(`#anr-user${userCnt}-div`).css('margin-top', '0.2em');
dragoLib.centerDialog('#anr-modal-dialog');
});
 
2,375 ⟶ 1,734行目:
$textarea.css('display','none').val('');
}
dragoLib.centerDialog('#anr-modal-dialog');
});
 
 
// ******************** AUXILIARY FUNCTIONS ********************
 
/**
* Function to check if an element is in an array
* @param {string} el
* @param {Array} arr
* @returns {boolean}
*/
function isInArray (el, arr) {
return arr.indexOf(el) !== -1;
}
 
/**
* Function to check if elements of an array are all contained in another array
* @param {Array} arr1
* @param {Array} arr2
* @returns {boolean}
*/
function arrayIsInArray(arr1, arr2) {
for (let i = 0; i < arr1.length; i++) {
if (!isInArray(arr1[i], arr2)) {
return false;
}
}
return true;
}
 
/**
* Function to check if a string contains a substring in an element of an array
* @param {string} str
* @param {Array} arr
* @returns {*} the first element matched in the array (if there's no match, returns undefined)
*/
function stringContainsElementInArray(str, arr) {
for (let i = 0; i < arr.length; i++) {
if (str.indexOf(arr[i]) !== -1) {
return arr[i];
}
}
}
 
/**
* Function to get the key of a value in an object
* @param {Object} object
* @param {*} value
* @returns {*} key
*/
function getKeyByValue(object, value) {
for (let key in object) {
if (object[key] == value) return key;
}
}
 
// Function to copy a string to the clipboard
function copyToClipboard(str) {
const $temp = $('<input>');
$('body').append($temp); // Create a temporarily hidden text field
$temp.val(str).select(); // Copy the text string into the field and select the text
document.execCommand('copy'); // Copy it to the clipboard
$temp.remove(); // Remove the text field
}
 
// Function to get today's date
function today() {
const d = new Date();
return d.getMonth() + 1 + '月' + d.getDate() + '日';
}
 
// Function to get the last day of the month
function lastDay(y, m){
return new Date(y, m + 1, 0).getDate();
}
 
/**
* String method to get rid of the U+200E space, in addition to the function of $.trim()
* @returns {string}
*/
String.prototype.trimANR = function() {
return this.replace(/\u200e/g, '').trim();
};
 
/**
* String method (alternative) to replace all occurences of a string with another
* (takes a replacer and a replacee as arguments)
* @returns {string}
*/
String.prototype.replaceAllANR = function() {
if (arguments.length %2 !== 0) {
return new Error('SyntaxError: replaceAllANR takes an even number of arguments.');
} else {
let replaced = '';
for (let i = 0; i < arguments.length; i = i + 2) {
if (i === 0) {
replaced = this.split(arguments[i]).join(arguments[i + 1]);
} else {
replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
}
}
return replaced;
}
};
 
String.prototype.escapeRegExpANR = function() { // Just a note: ^$.*+?()[]{}|
return this.replaceAllANR('(', '\\(', ')', '\\)', '.', '\\.');
}
 
})(); // Closure of anonymous function