// <nowiki>
/**
* close-mistakes.js - Скрипт для закрытия сообщений об ошибках
* Написан с использованием нейросетей claude-3-7-sonnet-20250219 и chatgpt-4o-latest-20250326
* Версия: 2.6
*/
$(function () {
const DEBUG = true;
function debug(...args) {
if (DEBUG) console.log('[CloseMistakes]', ...args);
}
const targetPages = [
'Википедия:Сообщения_об_ошибках',
'Участник:EyeBot/Сообщения_об_ошибках',
'Википедия:Фильтр_правок/Срабатывания',
'Википедия:Сообщения_об_ошибках_в_файлах_с_Викисклада',
'Участница:Megitsune-chan/Черновик' //Для тестов
];
const currentPage = mw.config.get('wgPageName');
if (!targetPages.includes(currentPage)) return;
if (mw.config.get('wgUserName') === null) return;
if (mw.user.isTemp() === true) return;
debug('Скрипт запущен на странице:', currentPage);
$('<style>').text(`
.error-closer-button {
margin-left: 10px;
font-size: 0.8em;
cursor: pointer;
padding: 2px 5px;
border: 1px solid #999;
border-radius: 3px;
background: #f8f9fa;
}
.error-closer-button:hover {
background: #eaecf0;
}
.error-closer-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border: 1px solid #a2a9b1;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
z-index: 1000;
width: 500px;
max-width: 90%;
}
.error-closer-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.error-closer-textarea {
width: 100%;
min-height: 150px;
margin: 10px 0;
padding: 8px;
border: 1px solid #a2a9b1;
}
.error-closer-buttons {
text-align: right;
}
.error-closer-button-submit,
.error-closer-button-cancel {
margin-left: 10px;
padding: 5px 15px;
border: 1px solid #a2a9b1;
border-radius: 2px;
cursor: pointer;
}
.error-closer-button-submit {
background: #36c;
color: white;
font-weight: bold;
}
.error-closer-button-cancel {
background: #f8f9fa;
}
`).appendTo('head');
// Загрузка полного текста страницы из кэша MediaWiki
new mw.Api().get({
action: 'parse',
page: currentPage,
prop: 'wikitext',
formatversion: 2
}).then(function (data) {
if (!data.parse?.wikitext) {
debug('Не удалось получить вики-текст страницы');
return;
}
const pageText = data.parse.wikitext;
const sectionRegex = /^(==\s*(.+?)\s*==)/gm;
const sections = [];
let match;
while ((match = sectionRegex.exec(pageText)) !== null) {
const fullHeader = match[1];
const title = match[2];
const position = match.index;
sections.push({ fullHeader, title, position });
}
debug(`Разделов найдено: ${sections.length}`);
const visibleHeaders = $('.mw-parser-output h2[id]');
processSectionsById(visibleHeaders);
}).catch(function (error) {
debug('Ошибка получения текста:', error);
});
function insertAtCursor($textarea, textToInsert) {
const textarea = $textarea[0]; // Получаем DOM-элемент
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const original = textarea.value;
// Вставляем текст в текущее положение курсора
const newValue = original.slice(0, start) + textToInsert + original.slice(end);
textarea.value = newValue;
// Перемещаем курсор за вставленный текст
const newPos = start + textToInsert.length;
textarea.setSelectionRange(newPos, newPos);
textarea.focus();
}
/**
* Основная обработка — вставка кнопок + фильтрация закрытых разделов
*/
function processSectionsById(visibleHeaders) {
new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections',
formatversion: 2
}).then(function (data) {
const sections = data.parse.sections;
if (!sections || !sections.length) {
debug('Нет доступных секций');
return;
}
debug(`Получено секций из API: ${sections.length}`);
sections.forEach(section => {
// Находим заголовок в DOM по id (anchor)
const sectionId = decodeURIComponent(section.anchor);
const header = visibleHeaders.filter(function () {
return $(this).attr('id') === section.anchor;
}).first();
if (!header.length) {
debug('Заголовок не найден в DOM для anchor', section.anchor);
return;
}
const sectionIndex = parseInt(section.index, 10);
const sectionTitle = section.line;
// Фильтрация по скрытым запросам (дополнительно запрашиваем текст секции)
new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
section: sectionIndex,
prop: 'wikitext',
formatversion: 2
}).then(function (textResult) {
const text = textResult?.parse?.wikitext || '';
const lower = text.toLowerCase();
if (
lower.includes('{{закрыто') ||
lower.includes('{{closed') ||
lower.includes('{{cls') ||
lower.includes('{{ecs')
) {
//debug('Пропущен закрытый раздел:', sectionTitle);
return;
}
// Добавляем кнопку
const closeButton = $('<span>')
.addClass('error-closer-button')
.text('Закрыть')
.on('click', function () {
openCloseDialog(sectionTitle, sectionIndex, section);
});
header.append(closeButton);
//debug(`Добавлена кнопка к разделу #${sectionIndex}: ${sectionTitle}`);
});
});
});
}
/**
* Получение текста конкретного раздела слоями (на основе срезов из полного текста)
*/
function extractSectionTextFromWikitext(fullText, sections, index) {
const start = sections[index].position;
const end = index + 1 < sections.length
? sections[index + 1].position
: fullText.length;
return fullText.slice(start, end);
}
/**
* Кнопки быстрых ответов
*/
const quickReplies = [
{
label: 'Сделано',
text: '{{сделано}}'
},
{
label: 'Исправлено',
text: '{{исправлено}}'
},
{
label: 'Возвращено',
text: '{{сделано|Ваша правка возвращена}}'
},
{
label: 'Добавлено',
text: '{{добавлено}}'
},
{
label: 'АИ',
text: '[[ВП:АИ|]]'
},
{
label: 'Не сделано',
text: '{{не сделано}}'
},
{
label: 'Не ошибка',
text: '{{не ошибка}}'
},
{
label: 'Отклонено',
text: '{{Отклонено}}'
},
];
/**
* Модальное окно с ответом и подтверждением
*/
function openCloseDialog(sectionTitle, sectionIndex, sectionInfo) {
debug('Открытие окна для раздела:', sectionTitle);
const overlay = $('<div>').addClass('error-closer-overlay');
const modal = $('<div>').addClass('error-closer-modal');
modal.append($('<h3>').text('Закрытие запроса: ' + sectionTitle));
const quickReplyContainer = $('<div>').css({
marginBottom: '5px',
display: 'flex',
flexWrap: 'wrap',
gap: '5px'
});
quickReplies.forEach(reply => {
const btn = $('<button>')
.addClass('error-closer-button')
.attr('type', 'button')
.text(reply.label)
.on('click', () => {
insertAtCursor(textarea, reply.text);
});
quickReplyContainer.append(btn);
});
modal.append(quickReplyContainer);
const textarea = $('<textarea>')
.addClass('error-closer-textarea')
.attr('placeholder', 'Введите ваш ответ (не обязательно). Подпись ставить не нужно!');
modal.append(textarea);
const buttonsDiv = $('<div>').addClass('error-closer-buttons');
const cancelBtn = $('<button>')
.addClass('error-closer-button-cancel')
.text('Отмена')
.on('click', () => {
overlay.remove();
modal.remove();
});
const submitBtn = $('<button>')
.addClass('error-closer-button-submit')
.text('Закрыть запрос')
.on('click', () => {
const responseText = textarea.val().trim();
closeErrorSection(sectionIndex, sectionTitle, responseText);
overlay.remove();
modal.remove();
});
buttonsDiv.append(cancelBtn, submitBtn);
modal.append(buttonsDiv);
$('body').append(overlay, modal);
textarea.focus();
}
/**
* Закрывает запрос шаблонами и ответом
*/
function closeErrorSection(sectionIndex, sectionTitle, responseText) {
debug('Закрытие секции:', sectionTitle);
new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
section: sectionIndex,
prop: 'wikitext',
formatversion: 2
}).then(function (sectionData) {
const oldText = sectionData?.parse?.wikitext;
if (!oldText) {
alert('Не удалось получить текущий текст.');
return;
}
let newText = oldText.replace(
/^(==\s*.+?\s*==\s*)/,
'$1{{закрыто}}\n'
);
if (responseText) {
newText += '\n\n* ' + responseText + ' ~~~~\n';
}
newText += '{{закрыто-конец}}';
const cleanTitle = cleanWikilinks(sectionTitle);
new mw.Api().postWithToken('csrf', {
action: 'edit',
title: mw.config.get('wgPageName'),
section: sectionIndex,
text: newText,
summary: 'Закрытие запроса «[[' + mw.config.get('wgPageName') + '#' + cleanTitle + '|' + cleanTitle + ']]» скриптом [[Участница:Megitsune-chan/close-mistakes|close-mistakes]]',
formatversion: 2
}).then(() => {
location.reload();
}).catch(function (error) {
alert('Ошибка при сохранении: ' + error);
debug('Ошибка API:', error);
});
}).catch(function (error) {
alert('Ошибка загрузки текста раздела.');
debug('Ошибка получения текста:', error);
});
}
/**
* Убирает вики-ссылки: [[Ссылка]] → Ссылка и [[Ссылка|Текст]] → Текст
*/
function cleanWikilinks(text) {
return text.replace(/\[\[([^\]|]+?\|)?([^\]]+?)\]\]/g, '$2');
}
});
// </nowiki>