Utente:Titore/VC.js
/**
* Aggiunge un link nella barra degli strumenti laterale
* per segnalare rapidamente un'utenza o un IP tra i vandalismi in corso.
*
* @author https://it.wikipedia.org/wiki/Utente:Titore and contributors
* @date 2019-09-17
* @license Dual-licensed under CC BY-SA 3.0 and GPL 3.0 (or any later version)
*/
(function (mw, $) {
'use strict';
var windowManager, dialog;
// API MediaWiki
const api = new mw.Api();
// Variabili MediaWiki
const conf = mw.config.get([
'wgRelevantUserName', // Presente in pagine attinenti all'utente (es. contributi, discussioni, pagina utente)
'wgUserName',
'wgArticlePath',
]);
// Utente da segnalare
const utente = conf.wgRelevantUserName;
// Utilizzatore dello script
const currentUser = conf.wgUserName;
// Pagina di servizio
const paginaSegnalazioni = 'Wikipedia:Vandalismi in corso';
// Campo oggetto
const oggetto = `[[Utente:Titore/VC|VC.js]]: segnalazione [[Speciale:Contributi/${utente}|${utente}]]`; //TODO: o tutto qui o tutto lì (inviaSegnalazione -> summary:)
// Motivazioni frequenti
const motivazioniFrequenti = [
'Reiterati vandalismi.',
'Avvisato, insiste.',
'Continua dopo il giallo.',
'Recidivo.',
'Evasione del blocco: ',
'Rimozione contenuti.',
'Utenza creata al solo scopo di vandalizzare.',
];
// Numero massimo di segnalazioni da elaborare per ricerca duplicati
var maxSegnalazioni = 5;
// Aggiunge link nella barra degli strumenti laterale
if (utente && utente!=currentUser) { // Evita di segnalare se stessi
var link = mw.util.addPortletLink(
'p-tb',
'#',
'Segnala vandalo',
't-segnalavandalo',
`Segnala ${utente} tra i vandalismi in corso`,
getAvailableAccessKey('v', 'i', 'c')); // Tasto di scelta rapida (es. Alt + Maiusc + v) e alternative in caso sia già in uso
$(link).click((event) => {
event.preventDefault();
avviaScript();
});
}
/**
* Avvia lo script.
*/
function avviaScript() {
mw.loader.using('oojs-ui-windows')
.done(() => finestraDiDialogo())
.fail(() => console.error('Impossibile avviare lo script VC.js.'));
}
/**
* Finestra di dialogo
*/
function finestraDiDialogo() {
// Subclass di ProcessDialog
function Dialog(config) {
Dialog.super.call(this, config);
}
OO.inheritClass(Dialog, OO.ui.ProcessDialog);
// Nome, titolo, action set
Dialog.static.name = 'dialogo';
Dialog.static.title = 'Nuova segnalazione';
Dialog.static.actions = [
{
flags: ['primary', 'progressive'], // Azione primaria, blu
label: 'Invia',
action: 'save',
disabled: true, // Disabilito finché non pronto //TODO: usare setAbilities()
},
{
flags: ['safe', 'close'],
label: 'Annulla',
}
];
// initialize(): aggiungo contenuto e layout
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true,
expanded: false,
});
this.content = new OO.ui.FieldsetLayout();
this.vandaloInput = new OO.ui.TextInputWidget({ //this.vandaloInput = new mw.widgets.UserInputWidget({ //non funziona da mobile
readOnly: true,
});
var style = ".vertical textarea{ resize: vertical; }"; // Solo resize verticale
mw.util.addCSS(style);
this.motivoInput = new OO.ui.MultilineTextInputWidget({
placeholder: 'Descrivi brevemente il tipo di vandalismo in corso.\nLa firma è automatica.',
rows: 3,
classes: ['vertical'],
/*multiline: true,
autosize: true,
maxRows: 3,
indicator: 'required',*/
});
// Dropdown
// TODO: meglio in una funzione? https://en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-defaultsummaries.js#L-66
var itemSelected; //<--- per strict
var self = this; //TODO: bind "this" to the function call instead
// https://stackoverflow.com/questions/337878/var-self-this#comment14371472_338106
// https://stackoverflow.com/a/10115970/8762088
this.dropDown = new OO.ui.DropdownWidget({
label: 'Motivazioni frequenti'
}),
// Evento che inserirà nel campo motivo l'opzione selezionata dal menu a tendina
itemSelected = function () { //TODO: inserire direttamente come anon func?
var motivazioneFrequente = self.dropDown.getMenu().findSelectedItem().getLabel();
self.motivoInput.setValue(motivazioneFrequente);
};
popolaDropdown(this.dropDown, motivazioniFrequenti);
// Fieldset
var fieldsetLayout = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.vandaloInput, {
label: 'Vandalo:',
align: 'top',
}),
new OO.ui.FieldLayout(this.motivoInput, {
label: 'Motivazione:',
align: 'top',
/*$overlay: this.$overlay,
help: 'Firma aggiunta automaticamente',
helpInline: true,*/
}),
new OO.ui.FieldLayout(this.dropDown, {
align: 'inline',
}),
]
});
this.content.addItems([fieldsetLayout]);
this.panel.$element.append(this.content.$element);
/* // Disclaimer
this.content.$element.append( '<small><p>Accertarsi che sia chiaro cosa si intende per <a href="/wiki/Wikipedia:Vandalismo" title="Wikipedia:Vandalismo">vandalismo</a>, che il vandalo sia stato avvertito e che i vandalismi siano recenti.</small>' );*/
this.$body.append(this.panel.$element);
this.dropDown.getMenu().on('select', itemSelected); // Quando viene scelta un'opzione dal menu
this.motivoInput.connect(this, { 'change': 'onMotivoInputChange' }); // Alla modifica del campo motivo
};
// Impedisce motivazione vuota
Dialog.prototype.onMotivoInputChange = function (value) {
this.actions.setAbilities({
save: !!value.length
});
};
// getSetupProcess(): passa dei dati all'apertura della finestra
Dialog.prototype.getSetupProcess = function (data) {
data = data || {};
return Dialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.vandaloInput.setValue(data.vandalo);
this.primoTentativo = true;
segnalatoDiRecente(paginaSegnalazioni).then(result => {
result ? console.log(`${utente} già segnalato.`) : console.log(`${utente} non ancora segnalato.`);
});
}, this);
};
// getActionProcess(): gestione delle azioni (es. 'save').
Dialog.prototype.getActionProcess = function (action) {
var parent = this; // Fallback to parent handler // TODO: <-------
const url = {
vc: conf.wgArticlePath.replace('$1', paginaSegnalazioni),
contribs: conf.wgArticlePath.replace('$1', `Special:Contribs/${this.vandaloInput.getValue()}`)
};
const err = {
giaBloccato: $(`<span>${this.vandaloInput.getValue()} <a href="${url.contribs}">risulta già bloccato</a>. Inviare comunque?</span>`),
giaSegnalato: $(`<span>${this.vandaloInput.getValue()} è già stato segnalato <a href="${url.vc}#footer">di recente</a>.</span>`)
};
return Dialog.super.prototype.getActionProcess.call(this, action)
// Impedisce se segnalato di recente
.next(function () {
// Alla pressione del tasto "Invia"
if (action === 'save') {
var promise = $.Deferred();
segnalatoDiRecente(paginaSegnalazioni).then(result => {
if (result) {
promise.reject(new OO.ui.Error(err.giaSegnalato, { recoverable: false }));
} else {
promise.resolve();
}
});
return promise;
}
}, this)
// Avvisa se già bloccato
.next(function () {
if (action === 'save') {
var promise = $.Deferred();
controllaBlocco(this.vandaloInput.getValue(), blocco => {
if (blocco && this.primoTentativo) { // Mostra il warning solo la prima volta
this.primoTentativo = false;
promise.reject(new OO.ui.Error(err.giaBloccato, { warning: true }));
console.log(`Segnalazione non inviata: ${parent.vandaloInput.getValue()} già bloccato.\n` +
`Il testo era: "${parent.motivoInput.getValue()}"`);
} else {
promise.resolve();
}
});
return promise;
}
}, this)
// Invia la segnalazione
.next(function () {
if (action === 'save') {
var promise = $.Deferred();
inviaSegnalazione(parent.vandaloInput.getValue(), parent.motivoInput.getValue())
.done(() => windowManager.closeWindow(dialog))
.fail((code) => { promise.reject(new OO.ui.Error(code)); console.error(`Segnalazione non inviata: ${code}`); });
return promise;
}
}, this);
};
//if (!windowManager) { // per non perdere contenuto alla chiusura della finestra //FIXME: cleanup dopo invio
// Crea e aggiunge nuovo window manager
windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
// Crea finestra di dialogo
dialog = new Dialog();
// Aggiunge la finestra al window manager
windowManager.addWindows([dialog]);
//}
// Apre la finestra
windowManager.openWindow(dialog, { vandalo: utente })
// Codice da eseguire dopo che la finestra è stata aperta
.opened.then(function () {
// Focus all'input per la motivazione
dialog.motivoInput.focus();
});
}
/**
* Invia la segnalazione.
*
* @param {string} utente - Vandalo da segnalare
* @param {string} motivo - Contenuto segnalazione
* @returns {object} jQuery.Promise
*/
function inviaSegnalazione(utente, motivo) {
const segnalazione = `{{vandalo|${utente}}} ${motivo} --~~\~~`;
// Data odierna, ordinale per il primo del mese
const dataOdierna = new Date()
.toLocaleDateString("it-IT", { day: 'numeric', month: 'long' })
.replace(/\b[1] /, '1º ');
// URL per link in notifica
const url = conf.wgArticlePath.replace('$1', paginaSegnalazioni);
// Per aggiungere sottosezione con data se non già esistente
return titoloUltimaSezione(paginaSegnalazioni)
.then(titoloUltimaSezione => {
return api.postWithEditToken({
action: 'edit',
title: paginaSegnalazioni,
appendtext: (dataOdierna == titoloUltimaSezione) ? ('\n\n' + segnalazione) :
('\n\n' + `=== ${dataOdierna} ===` + '\n' + segnalazione),
summary: `/* ${dataOdierna} */ ${oggetto}`
}).done(data => {
mw.notify($(`<span>${utente} è stato segnalato tra i <a href="${url}#footer">vandalismi in corso</a>.</span>`), {
title: 'Segnalato',
tag: 'notif'
});
console.log(`L'utente ha scritto "${motivo}" e ha fatto clic su "OK".`);
console.log(data);
});
});
}
/**
* Ottiene titoli e numeri sezioni.
*
* @param {string} pagina - Titolo della pagina di servizio
* @returns {object} jQuery.Promise - Sezioni
*/
function parsing(pagina) {
return api.get({
page: pagina,
action: 'parse',
prop: 'sections'
});
}
/**
* Ottiene il titolo dell'ultima sezione.
*
* @param {string} pagina - Titolo della pagina di servizio
* @returns {object} jQuery.Promise - Titolo dell'ultima sezione
*/
function titoloUltimaSezione(pagina) {
return parsing(pagina).then(data => {
return data.parse.sections[data.parse.sections.length - 1].line
// ordinale per il primo del mese
.replace(/\b[1] /, '1º ')
.replace('1° ', '1º ');
});
}
/**
* Ottiene l'ultimo numero di sezione della pagina.
*
* @returns {Promise<string>} Numero dell'ultima sezione
*/
function indexUltimaSezione(pagina) {
return parsing(pagina).then(data => data.parse.sections[data.parse.sections.length - 1].index);
}
/**
* Ottiene il wikitesto a partire dal numero della sezione.
*
* @param index Numero di sezione
* @returns {Promise<string>} wikitesto
*/
function wikitestoSezione(index, pagina) {
return api.get({
page: pagina,
action: 'parse',
prop: 'wikitext',
section: index,
formatversion: 2 //'cci tua
}).then(data => data.parse.wikitext);
//return data.parse.wikitext['*'] //https://stackoverflow.com/q/7610191/8762088; mw:API:JSON_version_2#Changes_to_JSON_output_format
}
/**
* Ottiene il wikitesto delle ultime due sezioni.
*
* @returns {Promise<array>} Array di sezioni
*/
function ultimeSezioni(pagina) {
return Promise.all([
// Wikitesto penultima sezione
indexUltimaSezione(pagina).then(index => wikitestoSezione(index - 1, pagina)),
// Wikitesto ultima sezione
indexUltimaSezione(pagina).then(index => wikitestoSezione(index, pagina))
]);
}
/**
* Controlla se il vandalo è già stato segnalato di recente.
* Solo ultime 2 sezioni e non oltre limite massimo.
* @param {string} pagina - Titolo della pagina di servizio
* @returns {Promise<boolean>} bool - Se è già stato segnalato
*/
function segnalatoDiRecente(pagina) {
return ultimeSezioni(pagina)
.then(wikitesto => {
var regex = /{{[\s\t\n\r]*vandalo[\s\t\n\r]*\|[\s\t\n\r]*([^}]*)\b/gi;
var segnalati = [];
var match = regex.exec(wikitesto);
while (match !== null) {
segnalati.push(match[1]);
match = regex.exec(wikitesto);
}
// Mi accerto che il limite massimo non superi il numero di segnalazioni
if (maxSegnalazioni > segnalati.length) {
maxSegnalazioni = segnalati.length;
}
//var segnalatiDiRecente = []
console.log(`Ultime segnalazioni: ${segnalati.slice(-maxSegnalazioni).join(", ")}.`);
for (var i = segnalati.length - maxSegnalazioni; i < segnalati.length; i++) {
// Se il vandalo è già presente tra le ultime segnalazioni
if (utente.toLowerCase() == segnalati[i].toLowerCase()) {
return true;
}
//segnalatiDiRecente.push(segnalati[i])
}
});
}
/**
* Controlla se l'utente è bloccato.
* @param {string} utente - Utente da controllare
* @param {function} blocco - Risultato
*/
function controllaBlocco(utente, blocco) {
api.get({
action: 'query',
list: 'blocks',
bkusers: utente,
bklimit: '1',
bkprop: 'by',
format: 'json'
}).done(data => {
blocco(data.query.blocks[0]);
});
}
/**
* Popola il menu a tendina.
*
* @param dropdown - Dropdown widget
* @param {string[]} opzioni - Scelte
*/
function popolaDropdown(dropdown, opzioni) {
dropdown.menu.addItems(opzioni.map((opzione) => {
return new OO.ui.MenuOptionWidget({ label: opzione });
}));
}
/**
* Controlla se un tasto di scelta rapida è disponibile.
*
* @param {string} key - Tasto da provare
* @returns {boolean} Se disponibile o meno
*/
function isAccessKeyAvailable(key) {
//$('[accesskey=v]')[0]
if (!$(`[accesskey=${key}]`)[0]) { // se non già in uso
return true;
}
}
/**
* Trova il primo tasto di scelta rapida disponibile tra quelli indicati
*
* @param {string[]} accessKeys - Tasti da provare
* @returns {string} Il primo tasto disponibile
*/
function getAvailableAccessKey(...accessKeys) {
for (var accessKey of accessKeys) {
if (isAccessKeyAvailable(accessKey)) {
return accessKey;
}
}
}
}(mediaWiki, jQuery));
// FIXME:
// Escludere se stessi può rendere difficile il debug in commons.js
// TODO:
// se già bloccato (es. allungare blocco filtro):
// chiedere conferma anche prima del clic a 'invia'
// visualizzare durata e autore blocco
// controllare che vandalo sia stato avvisato
// permettere di scegliere utente
// di selezionare più utenti
// controllare se utente esiste
// aggiungere anche mese se non presente
// motivazioni personalizzabili (tramite variabile da inserire in user script, JSON o option api)
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.