Участник:Skmp/LEP.js
Перейти к навигации
Перейти к поиску
Страница персонального оформления. У этого JS-кода есть документация: Участник:Skmp/LEP.
После сохранения очистите кэш браузера.
После сохранения очистите кэш браузера.
// ======================== ! ВНИМАНИЕ ! ========================
// Инструмент находится на ранней стадии разработки, и
// хоть ошибочных откатов и патрулирований у меня еще
// не возникало, имейте ввиду, что я НЕ беру на себя
// ответственность за ошибочные действия, совершённые
// при использовании этого инструмента.
//
// Больше информации про инструмент тут — Участник:Skmp/LEP/info
// ==============================================================
const LEP_VER = "0.6β";
const lep_html =
`
<style>
#lep-version {
float: right;
margin-top: 2px;
margin-bottom: 10px;
color: hsl(0, 0%, 50%);
font-size: 12px;
}
#lep {
position: relative;
display: flex;
height: 100%;
font-family: sans-serif;
}
#lep.darkmode {
background-color: hsl(228,4%,10%);
}
#lep.darkmode a {
color: hsl(217deg 91% 45%);
}
#lep > .edits-panel {
overflow: auto;
flex-shrink: 0;
width: 250px;
}
#lep > .edits-panel > .items {
display: flex;
flex-direction: column;
}
#lep > .edits-panel > .items > .new,
#lep > .edits-panel > .items > .blacklist,
#lep > .edits-panel > .items > .others,
#lep > .edits-panel > .items > .reds {
display: flex;
flex-direction: column-reverse;
}
@keyframes lep-item-show {
0% {
filter: invert(1);
}
25% {
filter: invert(0);
}
50% {
filter: invert(1);
}
100% {
filter: invert(0);
}
}
#lep > .edits-panel > .items > .blacklist > .item,
#lep > .edits-panel > .items > .reds > .item {
animation: lep-item-show 0.5s ease-in-out forwards;
}
#lep > .edits-panel > .items > .blacklist > .item {
color: crimson !important;
}
#lep > .edits-panel > .items .item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 9px 10px;
user-select: none;
cursor: pointer;
background-color: white;
font-size: 14px;
}
#lep.darkmode > .edits-panel > .items .item {
color: hsl(228,4%,80%);
background-color: hsl(228,4%,14%);
}
#lep > .edits-panel > .items .item.selected {
background-color: #eaf3ff;
color: #36c;
}
#lep.darkmode > .edits-panel > .items .item.selected {
background-color: hsl(228deg 28% 20%);
color: hsl(228deg 100% 68%);
}
#lep > .edits-panel > .items .item > .title {
width: 75%;
word-break: break-word;
}
#lep > .edits-panel > .items .item > .title > span {
color: gray;
font-size: 12px;
}
#lep > .edits-panel > .items .item > .score {
color: hsl(0, 0%, 50%);
font-size: 12px;
font-style: italic;
}
#lep > .edits-panel > .items .item > .score.red {
color: crimson;
font-weight: bold;
}
#lep.darkmode > .edits-panel > .items .item > .score.red {
color: #ff1e48;
}
#lep > .edits-panel > .items .item > .score.orange {
color: coral;
font-weight: bold;
}
#lep > .edits-panel > .items .item:hover {
background-color: rgba(234, 243, 255, 0.75);
}
#lep.darkmode > .edits-panel > .items .item:hover {
color: hsl(228,4%,90%);
background-color: hsl(228,4%,22%);
}
#lep > .main-panel {
display: flex;
flex-direction: column;
width: 100%;
border-left: 1px solid #a2a9b1;
}
#lep.darkmode > .main-panel {
border-left: 1px solid hsl(228,4%,25%);
}
#lep > .main-panel > .actions-panel {
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
width: 100%;
padding: 15px;
}
#lep > .main-panel > .actions-panel > .settings-panel {
display: flex;
align-items: flex-end;
height: 100%;
}
#lep > .main-panel > .actions-panel > .actions {
display: flex;
}
#lep > .main-panel > .actions-panel > .actions .buttons-container {
display: flex;
align-self: center;
}
#lep > .main-panel > .actions-panel > .actions .buttons-container > .vertical-container {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#lep > .main-panel > .actions-panel .normal-button {
padding: 4px 8px;
user-select: none;
cursor: pointer;
background-color: #f8f9fa;
border: 1px solid #a2a9b1;
border-radius: 3px;
color: #444;
font-size: 12px;
font-weight: bold;
text-align: center;
transition: 0.1s;
}
#lep.darkmode > .main-panel > .actions-panel .normal-button {
color: hsl(228,4%,70%);
border: 1px solid hsl(228,4%,15%);
background-color: hsl(228,4%,15%);
}
#lep > .main-panel > .actions-panel .normal-button:hover {
background-color: white;
}
#lep.darkmode > .main-panel > .actions-panel .normal-button:hover {
color: hsl(228,4%,85%);
border: 1px solid hsl(228,4%,22%);
background-color: hsl(228,4%,22%);
}
#lep > .main-panel > .actions-panel .normal-button:active {
background-color: hsl(0, 0%, 90%);
border: 1px solid hsl(212, 9%, 60%);
}
#lep > .main-panel > .actions-panel .normal-button.big {
font-size: 12.5px;
padding: 8px 14px;
}
#lep > .main-panel > .actions-panel .normal-button.small {
font-size: 12px;
font-weight: normal;
padding: 3px 6px;
}
#lep > .main-panel > .actions-panel .normal-button.disabled {
pointer-events: none;
background-color: #eaecf0;
color: #72777d;
border: 1px solid #c8ccd1 !important;
}
#lep.darkmode > .main-panel > .actions-panel .normal-button.disabled {
color: hsl(228,4%,40%);
border: 1px solid hsl(228,0%,20%) !important;
background-color: hsl(228,0%,20%);
}
#lep > .main-panel > .actions-panel .normal-button.blue {
border: 2px solid hsla(228, 80%, 50%, 1);
}
#lep > .main-panel > .actions-panel > .actions .big-button {
padding: 25px;
margin-right: 6px;
user-select: none;
cursor: pointer;
color: white;
background-color: #E82000;
border-radius: 5px;
font-size: 16px;
font-family: monospace;
text-transform: uppercase;
letter-spacing: 2px;
transition: 0.2s;
}
#lep > .main-panel > .actions-panel > .actions .big-button.hidden {
display: none;
}
#lep > .main-panel > .actions-panel > .actions .big-button.blue {
background-color: #1744FF;
}
#lep > .main-panel > .actions-panel > .actions .big-button.blue:hover {
background-color: hsl(228, 100%, 46%);
}
#lep > .main-panel > .actions-panel > .actions .big-button.disabled {
transition: 0s;
background-color: hsl(0, 0%, 70%);
pointer-events: none;
}
#lep > .main-panel > .actions-panel > .actions .big-button:hover {
background-color: hsl(8, 100%, 43%);
box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.15);
}
#lep > .main-panel > .actions-panel > .actions .big-button:active {
transition: 0s;
background-color: hsl(8, 100%, 37%);
box-shadow: 0px 2px 1px rgba(0, 0, 0, 0.15);
}
#lep > .main-panel > .actions-panel > .actions > .undo-container {
display: flex;
flex-direction: column;
padding: 0 15px;
width: 150px;
}
#lep > .main-panel > .actions-panel > .actions > .undo-container > .text {
color: hsl(0, 0%, 20%);
font-size: 12px;
font-style: italic;
margin-bottom: 4px;
}
#lep > .main-panel > .actions-panel > .actions > .undo-container > .buttons {
display: flex;
align-items: flex-start;
align-content: flex-start;
flex-wrap: wrap;
}
#lep > .main-panel > .actions-panel .buttons .normal-button {
margin: 0 3px 3px 0;
}
#lep > .main-panel > .edit-panel {
height: 100%;
width: 100%;
overflow: auto;
padding: 10px;
box-sizing: border-box;
border-top: 1px solid #a2a9b1;
}
#lep.darkmode > .main-panel > .edit-panel {
border-top: 1px solid hsl(228,4%,25%);
}
#lep.darkmode > .main-panel > .edit-panel .diff {
filter: invert(1);
}
#lep > .main-panel > .edit-panel > .edit-info {
display: flex;
align-items: center;
padding-bottom: 8px;
}
#lep > .main-panel > .edit-panel > .edit-info > span,
#lep > .main-panel > .edit-panel > .edit-info > a {
margin-right: 10px;
text-decoration: none;
}
#lep > .main-panel > .edit-panel > .edit-info > span:empty,
#lep > .main-panel > .edit-panel > .edit-info > a:empty {
margin-right: 0;
}
#lep > .main-panel > .edit-panel > .edit-info > a.red {
color: crimson;
}
#lep.darkmode > .main-panel > .edit-panel > .edit-info > a.red {
color: #ff1e48;
}
#lep > .main-panel > .edit-panel > .edit-info > a.green {
color: #00DD37;
}
#lep > .main-panel > .edit-panel > .edit-info > a:hover {
text-decoration: underline;
}
#lep-diff-title {
font-weight: bold;
}
#lep-diff-user {
font-style: italic;
font-size: 14px;
}
#lep-diff-summary {
font-size: 12px;
color: hsl(0, 0%, 20%);
}
#lep-diff-result {
font-weight: bold;
}
#lep-locked-indicator {
position: absolute;
top: 0;
right: 0;
margin: 10px;
color: hsl(0, 0%, 60%);
font-size: 12px;
}
/* Navigation */
#lep > .edits-panel > .navigation {
display: flex;
justify-content: space-between;
height: 34px;
border-bottom: 1px solid #a2a9b1;
}
#lep.darkmode > .edits-panel > .navigation {
border-bottom: 1px solid hsl(228,4%,25%);
}
#lep > .edits-panel > .navigation .button {
width: 48px;
font-family: monospace;
font-weight: bold;
text-align: center;
line-height: 34px;
cursor: pointer;
background-color: white;
border-right: 1px solid #a2a9b1;
}
#lep > .edits-panel > .navigation .button.left {
border-right: none;
border-left: 1px solid #a2a9b1;
}
#lep.darkmode > .edits-panel > .navigation .button {
color: hsl(228,4%,60%);
background-color: hsl(228,4%,15%);
border-right: 1px solid hsl(228,4%,25%);
}
#lep.darkmode > .edits-panel > .navigation .button.left {
border-right: none;
border-left: 1px solid hsl(228,4%,25%);
}
#lep.darkmode > .edits-panel > .navigation .button.invert > img {
filter: invert(1);
}
#lep > .edits-panel > .navigation .button:hover {
background-color: hsl(212, 9%, 95%);
}
#lep.darkmode > .edits-panel > .navigation .button:hover {
color: hsl(228,4%,80%);
background-color: hsl(228,4%,22%);
}
#lep > .edits-panel > .navigation .button:active {
background-color: hsl(212, 5%, 90%);
}
</style>
<div id="lep">
<div id="lep-locked-indicator"></div>
<div class="edits-panel">
<div class="navigation">
<div onclick="lep_nav_back()" class="button"><</div>
<div style="display: flex">
<div onclick="lep_clean_rollbacked()" class="button left"><i style="font-weight: normal">cln</i></div>
<div onclick="lep_show_settings()" class="button invert left">
<img src="https://upload.wikimedia.org/wikipedia/commons/5/58/Ic_settings_48px.svg" style="width: 13px; opacity: 0.75;">
</div>
</div>
</div>
<div class="items">
<div class="blacklist"></div>
<div class="reds"></div>
<div class="new"></div>
<div class="others"></div>
</div>
</div>
<div class="main-panel">
<div class="actions-panel">
<div class="actions">
<div class="buttons-container">
<div id="lep-rollback-btn" class="big-button" onclick="lep_rollback()">Откат</div>
<div class="vertical-container">
<div id="lep-patrol-btn" class="normal-button big disabled" onclick="lep_patrol()">Пат</div>
<div id="lep-patrol-btn" class="normal-button big" onclick="lep_diff()">Diff</div>
</div>
</div>
<div class="undo-container">
<div class="buttons">
<div class="normal-button small" onclick="lep_undo('bel', this)">БЕЛ</div>
<div class="normal-button small" onclick="lep_undo('ukr', this)">НАУКР</div>
<div class="normal-button small" onclick="lep_undo('src', this)">АИ?</div>
<div class="normal-button small" onclick="lep_undo('nn', this)">Не значимо</div>
</div>
</div>
<!-- V this should not be an undo-container V -->
<div class="undo-container" style="align-self: flex-start;">
<div class="buttons">
<div id="lep-del-btn" class="normal-button" onclick="lep_del_link()">Удалить</div>
<div id="lep-del-btn-o3" class="normal-button" onclick="lep_del_link('o3')">О3</div>
<div id="lep-del-btn-c5" class="normal-button" onclick="lep_del_link('c5')">C5</div>
</div>
</div>
</div>
<!-- <div class="settings-panel">
<div id="lep-settings-button" class="normal-button" onclick="lep_show_settings()">Настройки</div>
</div> -->
</div>
<div class="edit-panel">
<div class="edit-info">
<a target="_blank" id="lep-diff-title"></a>
<a target="_blank" id="lep-diff-user"></a>
<span target="_blank" id="lep-diff-summary"></span>
<a target="_blank" id="lep-diff-result"></a>
</div>
<div id="lep-page-html" style="font-size: 0.875em; line-height: 1.6"></div>
<table class="diff" style="font-size: 14px;">
<colgroup>
<col class="diff-marker">
<col class="diff-content">
<col class="diff-marker">
<col class="diff-content">
</colgroup>
<tbody id="lep-diff"></tbody>
</table>
</div>
</div>
</div>
<div id="lep-version"><span>v. ${ LEP_VER }${ window.LEP_DEV ? " dev" : "" }</span> · <a href="/wiki/ОУ:Skmp" target="_blank">Обратная связь</a> · <a href="/wiki/У:Skmp/LEP/info" target="_blank">Про инструмент</a></div>
`;
const lep_query_url = "/api.php?action=query&format=json&list=recentchanges&rcdir=older&rcprop=title%7Cids%7Cflags%7Ccomment%7Coresscores%7Cparsedcomment%7Ctags%7Cuser&rcshow=!bot&rclimit=25&rctype=edit%7Cnew";
var lep_last_time = new Date().toISOString();
var lep_locked = false;
var lep_el_lockedindicator;
var lep_el_btn_patrol;
var lep_el_btn_rollback;
var lep_el_diff_title;
var lep_el_diff_user;
var lep_el_diff_summary;
var lep_el_diff_result;
var lep_el_diff_container;
var lep_el_page_html;
var lep_el_items_blacklist;
var lep_el_items_reds;
var lep_el_items_new;
var lep_el_items_others;
var lep_edits = {};
var lep_blacklisted_users = [];
var lep_curr_edit = -1;
var lep_good_users = [];
var lep_sites = ["ru.wikipedia.org"];
var lep_nav_history = [];
var lep_nav_history_pos = -1;
// Загрузка добросовесных пользователей из localstorage
if(localStorage.hasOwnProperty("lep_good_users")) {
lep_good_users = localStorage.getItem("lep_good_users").split("|");
}
// Загрузка интервики для отслеживания из localstorage
if(localStorage.hasOwnProperty("lep_sites")) {
lep_sites = localStorage.getItem("lep_sites").split("|");
}
// Данные кнопок быстрой отмены
var lep_undo_data = {
bel: {
buttontext: "БЕЛ",
summary: "В Википедии принято использовать название «Белоруссия» (см. [[ВП:БЕЛ]])"
},
ukr: {
buttontext: "НАУКР",
summary: "В Википедии принято использовать форму «На Украине» (см. [[ВП:НАУКР]])"
},
src: {
buttontext: "АИ?",
summary: "[[ВП:АИ|Источник]]?"
},
nn: {
buttontext: "Не значимо",
summary: "Не [[ВП:ЗНАЧ|значимо]]"
}
};
// Настройки
var lep_config = JSON.parse(localStorage.getItem("lep_config")) || {
ores_min: 0.5,
ores_max: 0.94,
display_new: true,
display_new_suspicious: false,
tabclose_notify: true,
darkmode: false,
audio_notify: false,
audio_notify_volume: 0.33,
audio_notify_url: ""
};
var lep_tokens = {
};
// Валидация настроек
if(lep_config.ores_max < lep_config.ores_min) { lep_config.ores_min = 0.5; lep_config.ores_max = 0.94; }
if(lep_config.ores_min < 0.01 || lep_config.ores_min > 0.94) lep_config.ores_min = 0.5;
if(lep_config.ores_max < 0.01 || lep_config.ores_max > 0.99) lep_config.ores_max = 0.94;
if(!lep_config.audio_notify_volume || lep_config.audio_notify_volume > 1) lep_config.audio_notify_volume = 0.33;
// Звуковое уведомление
var lep_notification_sound = new Audio(lep_config.audio_notify_url || "https://notificationsounds.com/message-tones/appointed-529/download/mp3");
lep_notification_sound.volume = lep_config.audio_notify_volume;
function lep_getCsrfToken(token_type, callback) {
if(!token_type) token_type = "edit";
if(window.LEP_DEV) console.log("Getting token!", token_type);
$.getJSON(mw.config.get('wgScriptPath') + `/api.php?action=tokens&format=json&type=${ token_type }`, function (response) {
const response_token = response.tokens[`${ token_type }token`];
if(response_token) lep_tokens[token_type] = response_token;
if(callback) callback();
});
}
function lep_apiCall(url, params, csrf_token_type, callback) {
if(lep_tokens[csrf_token_type]) {
params.token = lep_tokens[csrf_token_type];
$.ajax({
url,
type: "POST",
crossDomain: true,
dataType: "json",
data: params,
success: function (response) {
if(response.error && window.LEP_DEV) console.log(response.error);
if(response.error && response.error.code === "badtoken") {
lep_getCsrfToken(csrf_token_type, function() {
lep_apiCall(url, params, csrf_token_type, callback);
});
} else {
callback(response);
}
},
error: function (xhr, status) {
console.log(status);
}
});
} else {
lep_getCsrfToken(csrf_token_type, function() {
lep_apiCall(url, params, csrf_token_type, callback);
});
}
}
// Сохранить настройки
function lep_config_save() {
// Поля ввода (числовые)
const inputs_num = document.querySelectorAll('#lep-config-dialog input[type="number"]');
for(const input of inputs_num) {
if(input.checkValidity() && input.value !== "") {
if(input.name === "audio_notify_volume") lep_config[input.name] = parseInt(input.value, 10) / 100;
else lep_config[input.name] = parseFloat(input.value, 10);
}
}
// Поля ввода (текст)
const inputs_text = document.querySelectorAll('#lep-config-dialog input[type="text"]');
for(const input of inputs_text) {
lep_config[input.name] = input.value;
}
// Сайты
const input_textarea = document.querySelector('#lep-config-dialog textarea');
localStorage.setItem("lep_sites", input_textarea.value.replace(/\n/g, "|"));
lep_sites = input_textarea.value.split("\n");
// Чекбоксы
const checkboxes = document.querySelectorAll('#lep-config-dialog input[type="checkbox"]');
for(const checkbox of checkboxes) {
lep_config[checkbox.name] = checkbox.checked;
}
// Обновить тёмный режим
if(lep_config.darkmode) {
document.getElementById("lep").classList.add("darkmode");
document.getElementById("mw-panel").style.filter = "invert(1)";
document.body.style.backgroundColor = "hsl(0, 0%, 15%)";
} else {
document.getElementById("lep").classList.remove("darkmode");
document.getElementById("mw-panel").style.filter = "unset";
document.body.style.backgroundColor = "unset";
}
window.localStorage.setItem("lep_config", JSON.stringify(lep_config));
// Обновить уведомление
lep_notification_sound = new Audio(lep_config.audio_notify_url || "https://notificationsounds.com/message-tones/appointed-529/download/mp3");
lep_notification_sound.volume = lep_config.audio_notify_volume;
}
// Навигация назад
function lep_nav_back() {
let len = lep_nav_history.length;
// История пуста
if(len === 0) return;
if(lep_nav_history_pos === -1 || lep_nav_history_pos >= len) {
lep_nav_history_pos = len - 2;
}
lep_show(lep_nav_history[lep_nav_history_pos], false, false, true);
lep_nav_history_pos -= 1;
}
var lep_stat_clean_undone = 0;
// Очистка отменённых правок
function lep_clean_rollbacked() {
if(confirm("Вы уверены, что хотите очистить уже отменённые правки?")) {
let els = document.querySelectorAll("#lep > .edits-panel > .items .item");
let next_time = 0;
// Каждая правка в левой панеле правок
for (const el of els) {
let id = el.id.split("-", 3)[2];
let user = lep_edits[id].user;
let pageid = lep_edits[id].pageid;
setTimeout(() => {
// Запрашиваем у сервера последние правки страницы
lep_apiCall(`/w/api.php?action=query&format=json&prop=revisions&pageids=${ lep_edits[id].pageid }&rvprop=comment%7Cuser%7Cids&rvlimit=8&rvendid=${ id }`, {}, "edit", a => {
let last_rev = a.query.pages[pageid].revisions[0];
if(last_rev.user === user) {
// Последняя правка была сделана подозрительным пользователем => правка не отменена
console.log(user, "not rollbacked")
} else {
// Кто-то редактировал страницу после подозрительного пользователя
for(const rev of a.query.pages[pageid].revisions) {
if(rev.comment.includes(user)) {
if(rev.comment.startsWith("[[ВП:Откат|откат]]") || rev.comment.startsWith("[[ВП:×|отмена]]") || rev.comment.includes("автоматическая отмена")) {
// В правке после правки подозрительного пользователя упоминается последний и присутствует строка "откат", "отмена", ...
console.log("ROLLBACKED!", rev.comment);
el.remove();
// Добавить в blacklist
lep_blacklisted_users.push(user);
lep_stat_clean_undone += 1;
break;
}
}
}
console.log(user, "not rollbacked");
}
});
}, next_time);
next_time += 500;
}
}
}
// Очистить список добросовестных пользователей
function lep_clear_good_users() {
localStorage.removeItem("lep_good_users");
lep_good_users = [];
const button = document.getElementById("lep_config_goodusers_clear")
button.innerText = "Готово!";
window.setTimeout(function() {
button.innerText = "Очистить список добросовестных пользователей";
}, 1500);
}
// Открыть окно настроек
function lep_show_settings() {
$("#lep-config-dialog").dialog("open");
}
// Заблокировать обновления (когда курсор нахидится внутри LEP)
function lep_lock() {
lep_el_lockedindicator.innerHTML = "locked";
lep_locked = true;
}
// Разблокировать обновления (когда курсор НЕ нахидится внутри LEP)
function lep_unlock() {
lep_el_lockedindicator.innerHTML = "";
lep_locked = false;
}
// Удалить выделение правки в левом меню
function lep_deselect() {
const selected_els = document.querySelectorAll("#lep > .edits-panel > .items .item.selected");
for(const el of selected_els) {
el.classList.remove("selected");
}
}
// Открыть страницу удаления
function lep_del_link(type) {
if(type === "c5") {
// Не значимо
window.open(`https://ru.wikipedia.org/w/index.php?title=${ lep_edits[lep_curr_edit].title }&action=delete&wpReason=[[ВП%3AКБУ%23С5|С5]]%3A нет доказательств энциклопедической значимости (автор [[Служебная:Вклад/${ lep_edits[lep_curr_edit].user }|${ lep_edits[lep_curr_edit].user }]])`);
} else if(type === "o3") {
// Вандализм
window.open(`https://ru.wikipedia.org/w/index.php?title=${ lep_edits[lep_curr_edit].title }&action=delete&wpReason=[[ВП%3AКБУ%23О3|О3]]%3A+страница%2C+созданная+для+[[ВП%3AВандализм|вандализма]] (автор [[Служебная:Вклад/${ lep_edits[lep_curr_edit].user }|${ lep_edits[lep_curr_edit].user }]])`);
} else {
// Пустая причина
window.open(`https://ru.wikipedia.org/w/index.php?title=${ lep_edits[lep_curr_edit].title }&action=delete&wpReason=(автор [[Служебная:Вклад/${ lep_edits[lep_curr_edit].user }|${ lep_edits[lep_curr_edit].user }]])`);
}
}
// Открыть страницу диффа
function lep_diff() {
if(lep_curr_edit > 0) window.open(`https://${ lep_edits[lep_curr_edit].site }/w/index.php?diff=${ lep_curr_edit }`);
}
// Патрулирование правки
function lep_patrol() {
// Отключаем кнопку
lep_el_btn_patrol.classList.add("disabled");
lep_el_btn_patrol.innerText = "...";
// Патрулируем
lep_apiCall(`https://${ lep_edits[lep_curr_edit].site }/w/api.php?action=review&format=json`, {
revid: lep_curr_edit,
}, "edit", function(response) {
// Отпатрулировано
lep_el_btn_patrol.innerText = "Готово";
});
}
// Откат правки
function lep_rollback() {
const rollback_revid = lep_curr_edit;
const pageid = lep_edits[lep_curr_edit].pageid;
const user = lep_edits[lep_curr_edit].user;
const site = lep_edits[lep_curr_edit].site;
// Добавить пользователя, совершившего правку, в чёрный список этой сессии
lep_blacklisted_users.push(user);
$.ajax({
url: `https://${ site }/w/api.php?action=query&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
prop: "revisions",
pageids: pageid,
rvprop: "ids|user"
},
success: function (response) {
// Проверяем, или нас не опередили
if(response.query.pages[pageid].revisions[0].user === user) {
// Отключаем кнопку отката
lep_el_btn_rollback.classList.add("disabled");
lep_el_btn_rollback.innerText = "...";
// Совершаем откат
lep_apiCall(`https://${ site }/w/api.php?action=rollback&format=json`, {
user: lep_edits[lep_curr_edit].user,
title: lep_edits[lep_curr_edit].title
}, "rollback", function(rollback_response) {
// Статус совершённого действия
lep_el_diff_result.innerText = "Откачено";
lep_el_diff_result.classList.add("green");
// Получаем дифф нашей правки
$.ajax({
url: `https://${ site }/w/api.php?action=compare&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
fromrev: rollback_response.rollback.revid,
torelative: "prev",
prop: "diff"
},
success: function (rollback_rev_response) {
// Дифф
lep_el_diff_container.innerHTML = rollback_rev_response.compare["*"];
// Текущая правка
// lep_curr_edit = rollback_rev_response.revid;
// Кнопка отката
lep_el_btn_rollback.innerText = "DONE";
// Удалить откаченную страницу из левой панели
const left_panel_item = document.getElementById("lep-rev-" + rollback_revid);
if(left_panel_item) left_panel_item.remove();
},
error: function (xhr, status) {
console.log(status);
}
});
}, site);
} else {
// Статус совершённого действия
lep_el_diff_result.innerText = "Ошибка отката. Кажется кто-то опередил.";
lep_el_diff_result.classList.add("red");
lep_el_btn_rollback.classList.add("disabled");
lep_el_btn_rollback.innerText = "NOPE";
}
},
error: function (xhr, status) {
console.log(status);
}
});
}
// Отмена правки с описанием
function lep_undo(type, el) {
const undo_revid = lep_curr_edit;
const pageid = lep_edits[lep_curr_edit].pageid;
const user = lep_edits[lep_curr_edit].user;
const site = lep_edits[lep_curr_edit].site;
if(site !== "ru.wikipedia.org") return;
// TODO
$.post(mw.config.get('wgScriptPath') + "/api.php?action=query&format=json", {
prop: "revisions",
pageids: pageid,
rvprop: "ids|user"
}, function(response) {
// Проверяем, или нас не опередили
if(response.query.pages[pageid].revisions[0].user === user) {
// Отключаем кнопку отмены
el.classList.add("disabled");
el.innerText = "...";
// Отмена правки
lep_apiCall(`https://${ site }/w/api.php?action=edit&format=json`, {
title: lep_edits[lep_curr_edit].title,
undo: undo_revid,
summary: `[[ВП:×|отмена]] правки ${ undo_revid } участника [[Special:Contribs/${ user }|${ user }]] ([[UT:${ user }|обс.]]) ${ lep_undo_data[type].summary }`
}, "edit", function(undo_response) {
// Статус совершённого действия
lep_el_diff_result.innerText = "Отменено";
lep_el_diff_result.classList.add("green");
// Получаем дифф нашей правки
$.post(mw.config.get('wgScriptPath') + "/api.php?action=compare&format=json", {
fromrev: undo_response.edit.newrevid,
torelative: "prev",
prop: "diff"
}, function(undo_rev_response) {
// Дифф
lep_el_diff_container.innerHTML = undo_rev_response.compare["*"];
// Текущая правка
// lep_curr_edit = undo_rev_response.revid;
// Включаем кнопку отмены обратно
el.classList.remove("disabled");
el.innerText = lep_undo_data[type].buttontext;
// Удалить отменённую страницу из левой панели
const left_panel_item = document.getElementById("lep-rev-" + undo_revid);
if(left_panel_item) left_panel_item.remove();
});
});
} else {
// Статус совершённого действия
lep_el_diff_result.innerText = "Ошибка отмены. Кажется кто-то опередил.";
lep_el_diff_result.classList.add("red");
el.innerText = lep_undo_data[type].buttontext;
}
});
}
// Показать правку
function lep_show(revid, del_link, new_page, is_from_nav) {
// Элемент левой панели
const el = document.getElementById("lep-rev-" + revid);
// Убираем выделение из левой панели
lep_deselect();
const this_edit = lep_edits[revid];
// Записываем в историю правок
if(is_from_nav !== true) {
lep_nav_history.push(revid);
lep_nav_history_pos = -1;
}
if(new_page) {
lep_el_page_html.innerHTML = "";
lep_el_diff_container.style.display = "none";
lep_el_page_html.style.display = "block";
} else {
lep_el_diff_container.innerHTML = "";
lep_el_diff_container.style.display = "contents";
lep_el_page_html.style.display = "none";
}
// Сброс кнопок и прочего
lep_el_btn_patrol.classList.add("disabled");
lep_el_btn_patrol.innerText = "Пат";
lep_el_diff_result.innerText = "";
lep_el_diff_result.className = "";
lep_el_btn_rollback.classList.remove("disabled");
lep_el_btn_rollback.innerText = "Откат";
// Удалять ли элемент из левой панели
if(is_from_nav !== true) {
if(del_link) el.remove();
else el.classList.add("selected");
}
// Прверяем, можно ли отпатрулировать
// TODO globalize
if(this_edit.site === "ru.wikipedia.org" && !new_page) {
$.post(mw.config.get('wgScriptPath') + "/api.php?action=query&format=json", {
titles: this_edit.title,
prop: "flagged|revisions"
}, function(response) {
if( response.query.pages[this_edit.pageid] &&
response.query.pages[this_edit.pageid].flagged &&
response.query.pages[this_edit.pageid].flagged.stable_revid === response.query.pages[this_edit.pageid].revisions[0].parentid
) {
lep_el_btn_patrol.classList.remove("disabled");
}
});
}
if(new_page) {
// Запрашиваем страницу
$.ajax({
url: `https://${ this_edit.site }/w/api.php?action=parse&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
prop: "text|displaytitle",
oldid: revid
},
success: function (response) {
// Название страницы
lep_el_diff_title.innerHTML = lep_edits[revid].title;
lep_el_diff_title.href = `https://${ this_edit.site }/wiki/${ lep_edits[revid].title}`;
// Пользователь, совершивший правку
lep_el_diff_user.innerHTML = lep_edits[revid].user;
lep_el_diff_user.href = `https://${ this_edit.site }/wiki/Special:Contributions/${ lep_edits[revid].user }`;
// Описание правки
lep_el_diff_summary.innerHTML = lep_edits[revid].parsedcomment;
// Дифф
if(response.error && response.error.code === "nosuchrevid") {
lep_el_page_html.innerHTML = "";
lep_el_diff_result.innerText = "Невозможно получить правку. Возможно, страница была удалена.";
lep_el_diff_result.classList.add("red");
} else {
lep_el_page_html.innerHTML = response.parse.text["*"];
}
// Текущая правка
lep_curr_edit = revid;
},
error: function (xhr, status) {
console.log(status);
}
});
} else {
// Запрашиваем правку
$.ajax({
url: `https://${ this_edit.site }/w/api.php?action=compare&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
fromrev: revid,
torelative: "prev",
prop: "diff|parsedcomment"
},
success: function (response) {
// Название страницы
lep_el_diff_title.innerHTML = lep_edits[revid].title;
lep_el_diff_title.href = `https://${ this_edit.site }/wiki/${ lep_edits[revid].title}`;
// Пользователь, совершивший правку
lep_el_diff_user.innerHTML = lep_edits[revid].user;
lep_el_diff_user.href = `https://${ this_edit.site }/wiki/Special:Contributions/${ lep_edits[revid].user }`;
// Описание правки
lep_el_diff_summary.innerHTML = lep_edits[revid].parsedcomment;
// Дифф
if(response.error && response.error.code === "nosuchrevid") {
lep_el_diff_container.innerHTML = "";
lep_el_diff_result.innerText = "Невозможно получить правку. Возможно, страница была удалена.";
lep_el_diff_result.classList.add("red");
} else {
lep_el_diff_container.innerHTML = response.compare["*"];
}
// Текущая правка
lep_curr_edit = revid;
},
error: function (xhr, status) {
console.log(status);
}
});
}
}
// Получить правки (раз в 5 секунд)
function lep_getEdits(site) {
$.ajax({
url: `https://${ site }/w${ lep_query_url }&rcend=${ encodeURIComponent(lep_last_time) }&rcstart=${ encodeURIComponent(new Date((new Date()) - 10000).toISOString()) }`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
success: async function (response) {
for(const edit of response.query.recentchanges) {
edit.site = site;
// Пропустить правку, если она уже есть в lep_edits с готовой оценкой ORES
if(lep_edits[edit.revid]) {
if(lep_edits[edit.revid].oresscores.length !== 0) continue;
if(lep_edits[edit.revid].oresscores.length === 0 && edit.oresscores.length === 0) continue;
}
// Пропустить, если новая страница от добросовестного участника
if(edit.type === "new") {
if(lep_good_users.includes(edit.user)) {
if(window.LEP_DEV) console.log("skipping good user: ", edit.user);
continue;
} else {
$.ajax({
url: `https://${ site }/w/api.php?action=query&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
list: "users",
usprop: "groups",
ususers: edit.user
},
success: function (user_response) {
const user_groups = user_response.query.users[0].groups;
if(user_groups !== undefined) {
if(user_groups.includes("editor") ||
user_groups.includes("autoeditor")
) {
if(window.LEP_DEV) console.log("adding good user from " + site + " to the list: ", edit.user);
lep_good_users.push(edit.user);
if(!localStorage.hasOwnProperty("lep_good_users")) {
localStorage.setItem("lep_good_users", "Jimbo Wales");
}
const new_list = localStorage.getItem("lep_good_users") + "|" + edit.user;
localStorage.setItem("lep_good_users", new_list);
}
}
},
error: function (xhr, status) {
console.log(status);
}
});
}
}
lep_edits[edit.revid] = edit;
// ORES score
let score;
let ores_NA = false;
let force_show = false;
if(edit.oresscores && edit.oresscores.length !== 0) {
score = edit.oresscores.damaging.true;
} else {
ores_NA = true;
await new Promise(function(resolve) {
$.ajax({
url: `https://${ site }/w/api.php?action=query&format=json`,
type: "GET",
crossDomain: true,
dataType: "jsonp",
data: {
list: "users",
usprop: "groups",
ususers: edit.user
},
success: function (user_response) {
const user_groups = user_response.query.users[0].groups;
if(user_groups !== undefined) {
if(!user_groups.includes("editor") &&
!user_groups.includes("autoeditor") &&
!user_groups.includes("autoreview")
) {
force_show = true;
}
} else {
force_show = true;
}
resolve();
},
error: function (xhr, status) {
console.log(status);
}
});
});
score = 0;
}
if(window.LEP_DEV) console.log(edit.revid, score, force_show, edit.user, site);
// Новый элемент левой панели
let new_item = document.createElement("div");
new_item.id = "lep-rev-" + edit.revid;
new_item.className = "item";
if(edit.type === "new") {
new_item.onclick = () => { lep_show(edit.revid, true, true) };
} else {
new_item.onclick = () => { lep_show(edit.revid, true) };
}
new_item.innerHTML += '<div class="title"><span>[' + site.split(".")[0] + ']</span> ' + edit.title
+ '</div><div class="score ' + ((score >= lep_config.ores_min || force_show) ? ' red' : (edit.type === 'new' ? ' orange' : '')) + '">' + (edit.type === 'new' ? 'new' : (force_show ? "N/A" : (score || "???"))) + '</div>';
let in_blacklist = lep_blacklisted_users.includes(edit.user);
// Определяем, записывать ли в левое меню
if(in_blacklist) {
// Пользователя уже откатывали
lep_el_items_blacklist.appendChild(new_item);
if(lep_config.audio_notify) lep_notification_sound.play();
if(!lep_locked) lep_show(edit.revid);
} else if(force_show || (score >= lep_config.ores_min && score <= lep_config.ores_max)) {
// ORES score выше порога
lep_el_items_reds.appendChild(new_item);
if(lep_config.audio_notify) lep_notification_sound.play();
if(!lep_locked) lep_show(edit.revid);
} else if(lep_config.display_new && edit.type === "new"
&& !/^(Обсуждение|Обсуждение участника|Категория):+/.test(edit.title)) {
// Новая страница
if(lep_config.display_new_suspicious) {
// Отображать как подозрительную правку
lep_el_items_reds.appendChild(new_item);
if(lep_config.audio_notify) lep_notification_sound.play();
if(!lep_locked) lep_show(edit.revid, false, true);
} else {
lep_el_items_new.appendChild(new_item);
}
} else if(!score && !ores_NA) {
// ORES score ещё не готов
lep_el_items_others.appendChild(new_item);
}
}
lep_last_time = new Date((new Date()) - 10000).toISOString();
},
error: function (xhr, status) {
console.log(status);
}
});
}
// Иньекция HTML
function lep_inject() {
// @TODO
// Загрузка стилей
let mediawiki_css = document.createElement("link");
mediawiki_css.href = "https://www.mediawiki.org/w/load.php?modules=mediawiki.legacy.shared|mediawiki.diff.styles&only=styles";
mediawiki_css.rel = "stylesheet";
document.head.appendChild(mediawiki_css);
// Удаление некоторых элементов интерфейса mediawiki
document.getElementById("mw-head").remove();
document.getElementById("mw-head-base").remove();
document.getElementById("mw-page-base").remove();
document.getElementById("footer").remove();
const content_el = document.getElementById("content");
content_el.style.borderColor = "#a2a9b1";
content_el.style.padding = "0";
content_el.style.height = "100%";
// Изменение названия вкладки на "(LEP) Участник:..."
window.document.title = "(LEP) " + window.document.title;
// Иньекция HTML
document.querySelector("#content.mw-body").innerHTML = lep_html;
// Ивенты для блокировки и разблокировки обновлений
document.getElementById("lep").addEventListener("mouseenter", lep_lock);
document.getElementById("lep").addEventListener("mouseleave", lep_unlock);
// Уведомление о закрытии вкладки
if(lep_config.tabclose_notify) {
window.onbeforeunload = function(e) {
e = e || window.event;
if(e) e.returnValue = '1';
return '1';
};
}
// Тёмный режим
if(lep_config.darkmode) {
document.getElementById("lep").classList.add("darkmode");
document.getElementById("mw-panel").style.filter = "invert(1)";
document.body.style.backgroundColor = "hsl(0, 0%, 15%)";
}
// Элементы интерфейса
lep_el_lockedindicator = document.getElementById("lep-locked-indicator");
lep_el_btn_patrol = document.getElementById("lep-patrol-btn");
lep_el_btn_rollback = document.getElementById("lep-rollback-btn");
lep_el_diff_title = document.getElementById("lep-diff-title");
lep_el_diff_user = document.getElementById("lep-diff-user");
lep_el_diff_summary = document.getElementById("lep-diff-summary");
lep_el_diff_result = document.getElementById("lep-diff-result");
lep_el_diff_container = document.getElementById("lep-diff");
lep_el_page_html = document.getElementById("lep-page-html");
lep_el_items_blacklist = document.querySelector("#lep > .edits-panel > .items > .blacklist");
lep_el_items_reds = document.querySelector("#lep > .edits-panel > .items > .reds");
lep_el_items_new = document.querySelector("#lep > .edits-panel > .items > .new");
lep_el_items_others = document.querySelector("#lep > .edits-panel > .items > .others");
}
mw.loader.using('mediawiki.util').done(function() {
// Ссылка в левом меню интерфейса MediaWiki
var listItem = document.querySelector("#p-tb > div > ul");
if(listItem)listItem.innerHTML += `<li id="t-lep"><a href="/wiki/Участник:Skmp/LEP" title="Запустить LEP [alt-shift-e]" accesskey="e">Запустить LEP</a></li>`;
if(mw.config.get('wgPageName') === "Участник:Skmp/LEP") {
// Запуск инструмента
lep_inject();
// Создаём интервал новых правок
setInterval(function() {
for(const site of lep_sites) {
lep_getEdits(site);
}
}, 5000);
}
});
// Окно настроек
mw.loader.using( 'jquery.ui', function() {
$(`<div id="lep-config-dialog">\
<fieldset name="filtration"><legend>Фильтрация правок</legend>\
<p style="color: hsl(0, 0%, 40%); font-style: italic; font-size: 11px; margin: 2px 0 10px 0">Правка будет отображена, если её оценка <a target="_blank" href="https://www.mediawiki.org/wiki/ORES"><i>ORES</i></a> удволитворит критериям ниже.</p>\
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0">\
<span>Минимальное значение <small style="color: gray">(0.5)</small></span>\
<input value="${ lep_config.ores_min }" style="width: 50px" name="ores_min" type="number" min="0.01" max="1" step="0.01">\
</div>\
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0">\
<span>Максимальное значение <small style="color: gray">(0.95)</small></span>\
<input value="${ lep_config.ores_max }" style="width: 50px" name="ores_max" type="number" min="0.01" max="1" step="0.01">\
</div>\
<div style="display: flex; align-items: center">\
<input ${ lep_config.display_new ? 'checked' : "" } style="margin: 0 5px 0 0" type="checkbox" name="display_new">\
<span>Отображать создание новых страниц</span>\
</div>\
<div style="display: flex; align-items: center; margin: 5px 0 0 10px">\
<input ${ lep_config.display_new_suspicious ? 'checked' : "" } style="margin: 0 5px 0 0" type="checkbox" name="display_new_suspicious">\
<span>Отображать как подозрительные</span>\
</div>\
</fieldset>\
<fieldset name="notification"><legend>Звуковое уведомление</legend>\
<div style="display: flex; align-items: center; margin: 4px 0">\
<input ${ lep_config.audio_notify ? 'checked' : "" } style="margin: 0 5px 0 0" type="checkbox" name="audio_notify">\
<span>Включить звуковые уведомления</span>\
</div>\
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0">\
<span>Громкость уведомления <small style="color: gray">(1% — 100%)</small></span>\
<input value="${ lep_config.audio_notify_volume * 100 }" style="width: 50px" name="audio_notify_volume" type="number" min="1" max="100" step="1">\
</div>\
<div style="display: flex; justify-content: space-between; align-items: center; margin: 4px 0">\
<span>Ссылка на аудиофайл <small style="color: gray">(<a target="_blank" href="https://notificationsounds.com/notification-sounds">библиотека</a>)</small></span>\
<input value="${ lep_config.audio_notify_url || '' }" style="width: 150px" name="audio_notify_url" type="text">\
</div>\
<p style="color: hsl(0, 0%, 40%); font-style: italic; font-size: 11px; margin: 10px 0 0 0">Ссылку на пользовательский аудиофайл указывать не обязательно.</p>\
</fieldset>\
<fieldset name="other"><legend>Прочее</legend>\
<div style="display: flex; align-items: center; margin: 4px 0">\
<input ${ lep_config.tabclose_notify ? 'checked' : "" } style="margin: 0 5px 0 0" type="checkbox" name="tabclose_notify">\
<span>Предупреждать о закрытии вкладки</span>\
</div>\
<!-- Включить darkmode. display should be flex -->\
<div style="display: none; align-items: center; margin: 4px 0">\
<input ${ lep_config.darkmode ? 'checked' : "" } style="margin: 0 5px 0 0" type="checkbox" name="darkmode">\
<span>Тёмный режим</span>\
</div>\
<button style="margin-top: 4px" id="lep_config_goodusers_clear" onclick="lep_clear_good_users()">Очистить список добросовестных пользователей</button>\
</fieldset>\
<fieldset name="sites_set"><legend>Сайты (WIP)</legend>\
<div style="margin: 4px 0">\
<span>Список сайтов для слежения (разделять новой строкой)</span>\
<textarea style="margin-top: 5px; width: 100%; max-width: 100%; resize: none" rows="5" name="sites_list">${ lep_sites.join("\n") }</textarea>\
</div>\
</fieldset>\
`)
.dialog({
title: "Настройки LEP",
closeOnEscape: true,
autoOpen: false,
resizable: false,
width: 450,
dialogClass: "lep-config-dialog-root",
buttons: [{
text: "Сохранить",
click: function() {
lep_config_save();
$( this ).dialog( "close" );
}
}]
});
});