User:Darkoneko/nekotb fc 1.1.js

//une extension de Date pour qu'il comprenne le format ISO8601 utilisé par l'API (ya une fonction native mais seulement sur firefox 3.5+) // http://dansnetwork.com/2008/11/01/javascript-iso8601rfc3339-date-parser/ Date.prototype.setISO8601 = function(dString){ var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)?(\d\d)?(\d\d)?(Z)/;

if (dString.toString.match(new RegExp(regexp))) { var d = dString.match(new RegExp(regexp)); var offset = 0;

this.setUTCDate(1); this.setUTCFullYear(parseInt(d[1],10)); this.setUTCMonth(parseInt(d[3],10) - 1); this.setUTCDate(parseInt(d[5],10)); this.setUTCHours(parseInt(d[7],10)); this.setUTCMinutes(parseInt(d[9],10)); this.setUTCSeconds(parseInt(d[11],10)); } else { this.setTime(Date.parse(dString)); } return this; };

Date.prototype.getYYYYMMDD = function{ var month = this.getMonth+1 if(month < 10) month = "0"+month var day = this.getDate if(day < 10) day = "0"+day return ( this.getFullYear +"-"+ month +"-"+ day ); };

//essai d'extension du DOM Element. ça rend de facto le script incompatible avec IE7- et Safari 2 Element.prototype.createAndAppendTextNode = function(p_txt) { return this.appendChild( document.createTextNode( p_txt ) ) }

//creation d'un element + attributs + éventuels node texte dedans ; et ajout au node parent Element.prototype.createAndAppendElement = function(p_el, p_attributs, p_textnode) { var xx = this.appendChild( document.createElement( p_el ) ) for(key in p_attributs) { xx.setAttribute( key, p_attributs[key] ) }  if(p_textnode) { xx.createAndAppendTextNode( p_textnode ) }  return xx }

//supprime tous les sous nodes d'un node (pas besoin d'être récursif). Element.prototype.removeChildNodes = function { while (this.hasChildNodes ) { this.removeChild(this.firstChild); } }

/* Auteur : user:Darkoneko tous les id css sont préfixées de nekotb_fc (pour "NEKO ToolBox - Fusionneur de Contribs") pour eviter les conflits avec d'autres scripts

var nekotb_fc = new Object

nekotb_fc.bgcolor_list = new Array( //utilisés pour la coloration des lignes de chaque user "lightblue", "yellow",  "lightgreen",  "white",  "#FCC",  "#CCC",  "lightyellow",  "green",  "orange",  "#88F" ) //note : pas de virgule après le dernier terme ou ça plante !

nekotb_fc.user_list = new Array //stocke la liste des utilisateurs fusionnés nekotb_fc.contrib_limit_per_user = 7500; //nb max de contribs récupérées par compte nekotb_fc.edit_clash_limit = 5; //(en minutes) espacement sous lequel des edits de 2 comptes différents sont considérés comme en collision nekotb_fc.timeline_nb_day_per_line = 5 //nombre de jours affichés par ligne (en + du nom des comptes) en mode timeline

nekotb_fc.oldest_timestamp = false; nekotb_fc.newest_timestamp = false; nekotb_fc.user_contribs = new Array nekotb_fc.every_contrib_ordered = new Array

nekotb_fc.addLinkToLeftBar = function { var ul = document.getElementById("p-navigation").getElementsByTagName("ul")[0] var li = ul.createAndAppendElement("li") li.createAndAppendElement( "a", {href:'#', 'onclick':"return nekotb_fc.init"}, '@Fusion contribs 1.1' )

} $(nekotb_fc.addLinkToLeftBar)

nekotb_fc.init = function { document.getElementById("firstHeading").firstChild.nodeValue = "Neko toolbox : fusionneur de contribs, v1.1.2 du 06/09/2015"

var content = document.getElementById("bodyContent") content.removeChildNodes   //supprimer le contenu initial de la zone 'article".

var root = content.createAndAppendElement("div", {'id':"nekotb_fc_formulaire"} ) root.createAndAppendElement("h2", {}, "Zone temporelle à inspecter")

//on laisse à l'user le choix dans la date curYear = new Date.getFullYear; root.createAndAppendTextNode("Fusionner entre le ") root.createAndAppendElement("input", {'id':"nekotb_fc_date_debut", 'value':curYear+"-01-01"} ) root.createAndAppendTextNode(" et le ") root.createAndAppendElement("input", {'id':"nekotb_fc_date_fin", 'value':curYear+"-12-31"})

root.createAndAppendElement("h2", {}, "Comptes à fusionner")

//ajout des users var form = root.createAndAppendElement("form", {"onsubmit":"return nekotb_fc.add_user;"} ) form.createAndAppendElement("input", {'id':"nekotb_fc_user_input"} ) form.createAndAppendTextNode(" (appuyez sur entrée pour ajouter)") root.createAndAppendElement("ul", {'id':"nekotbfc_liste_users"} ) //la liste des users ajoutés sera affichée dans cet element root.createAndAppendElement("hr") root.createAndAppendElement( "a", { href:"#", "onclick":"return nekotb_fc.show_fusion" }, "=> lancer la fusion <=" )

//réglages divers root.createAndAppendElement("h2", {}, "Réglages optionnels divers")

root.createAndAppendElement("h3", {}, "Nombre max de contributions récupérées par utilisateur") root.createAndAppendElement("input", { 'id' : "nekotb_fc_contrib_limit_per_user", "value": nekotb_fc.contrib_limit_per_user } ) root.createAndAppendTextNode(" (sera arrondi au multiple de 500 supérieur)")

root.createAndAppendElement("h3", {}, "indicateur de collisions") root.createAndAppendTextNode("Ecart maximal de temps pour que des éditions de 2 users différents soient considérées comme entrant en 'collision' : ") root.createAndAppendElement("br") root.createAndAppendElement("input", { 'id' : "nekotb_fc_edit_clash_limit", "value":nekotb_fc.edit_clash_limit }) root.createAndAppendTextNode(" minutes")

root.createAndAppendElement("h3", {}, "timeline") root.createAndAppendTextNode("Nombre de jours par ligne") root.createAndAppendElement("br") root.createAndAppendElement("input", { 'id' : "nekotb_fc_timeline_nb_day_per_line", "value":nekotb_fc.timeline_nb_day_per_line })

return false }

nekotb_fc.add_user = function { var input = document.getElementById("nekotb_fc_user_input") var name = input.value input.value = '' //on vide le champ if(name.length < 1 ) return false //si champ vide, on ignore silencieusement

var ul = document.getElementById("nekotbfc_liste_users") var randomNumber = Math.floor(Math.random*100001) //generation d'un id [a priori] unique, comme le nom peut contenir des chars non valides var li = ul.createAndAppendElement("li", {'id':"nekotb_fc_"+randomNumber, "onclick":"nekotb_fc.remove_user('nekotb_fc_"+randomNumber +"')"} ) li.createAndAppendTextNode(name) var img = li.createAndAppendElement("img", {'src':"http://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Pictogram_voting_delete.svg/15px-Pictogram_voting_delete.svg.png"} ) nekotb_fc.user_list.push( name.replace(/ /g, "_") )

return false }

nekotb_fc.remove_user = function(idnode) { var node = document.getElementById(idnode) var name = node.firstChild.nodeValue.replace(/ /g, "_") node.parentNode.removeChild(node)

for(var a=0 ; a < nekotb_fc.user_list.length ; a++) { if(nekotb_fc.user_list[a] == name) { nekotb_fc.user_list.splice(a, 1); break; }  }   return false }

nekotb_fc.back_to_form = function { var div2 = document.getElementById("nekotb_fc_fusion") div2.parentNode.removeChild(div2) //supprimer le div de resultat complement document.getElementById("nekotb_fc_formulaire").style.display = "block" return false }

nekotb_fc.fetch_and_verify_parameters = function { if( nekotb_fc.user_list.length < 1 ) { alert("aucun utilisateur selectionné") return false }

var reg_int = new RegExp(/^[0-9]+$/)

//recuperer les params nekotb_fc.contrib_limit_per_user = document.getElementById("nekotb_fc_contrib_limit_per_user").value if( ! reg_int.test(nekotb_fc.contrib_limit_per_user) ) { alert("la limite de contributions récupérées par compte doit être un nombre entier") return false }

nekotb_fc.edit_clash_limit = document.getElementById("nekotb_fc_edit_clash_limit").value if( ! reg_int.test(nekotb_fc.edit_clash_limit ) ) { alert("l'écart pour l'indicateur de clash doit etre un nombre entier") return false }

nekotb_fc.timeline_nb_day_per_line = document.getElementById("nekotb_fc_timeline_nb_day_per_line").value if( ! reg_int.test(nekotb_fc.timeline_nb_day_per_line ) ) { alert("le nombre de jour par ligne de la timeline doit etre un nombre entier") return false }

var reg_date = new RegExp(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)

var debut = document.getElementById("nekotb_fc_date_debut").value if( ! reg_date.test(debut) ) { alert("date de début est incorrect") return false }  nekotb_fc.oldest_timestamp = debut + "T00:00:00Z"

var fin = document.getElementById("nekotb_fc_date_fin").value if( ! reg_date.test(fin) ) { alert("date de fin est incorrect") return false }  nekotb_fc.newest_timestamp = fin + "T23:59:59Z"

return true }

nekotb_fc.show_fusion = function { if( ! nekotb_fc.fetch_and_verify_parameters ) return false;

nekotb_fc.user_count = 0 //mise ou remise à 0 du compteur utilisé pour la colorisation

document.getElementById("nekotb_fc_formulaire").style.display = "none" //masquer le div du formulaire (sans le supprimer)

//creer le div qui contiendra les resultats var div2 = document.getElementById("bodyContent").createAndAppendElement( "div", {'id':"nekotb_fc_fusion"} ) div2.createAndAppendElement( "a", {'href':"#", 'onclick':"return nekotb_fc.back_to_form"}, "revenir au formulaire" )

div2.createAndAppendElement( "h2", {}, "Récupération des contributions" ) div2.createAndAppendElement("ul", { 'id' : "nekotb_fc_fusion_user_list" }) //utilisé pour jouer avec l'affichage en temps réel de la recup de contribs div2.createAndAppendElement("h2", {}, "Liste des contributions fusionnées") div2.createAndAppendTextNode("Note : les heures indiquées sont en UTC")

//recuperation des contributions par user for(var a=0, len = nekotb_fc.user_list.length ; a < len ; a++) { nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ] = nekotb_fc.get_contribution_list( nekotb_fc.user_list[a]) nekotb_fc.every_contrib_ordered = nekotb_fc.every_contrib_ordered.concat( nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ] ); nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ].sort( function(a, b){ return a['revid'] - b['revid'] } ) //tri chronologique ascendant }  nekotb_fc.every_contrib_ordered.sort( function(a, b){ return b['revid'] - a['revid'] } ) //tri chronologique descendant var len = nekotb_fc.every_contrib_ordered.length var dat = new Date

if( len > 0 ) { //test de creation de ligne var first_timestamp = dat.setISO8601(nekotb_fc.oldest_timestamp).getTime //(car antéchronologique) var last_timestamp = dat.setISO8601(nekotb_fc.newest_timestamp).getTime

nekotb_fc.show_timeline(first_timestamp, last_timestamp) }  return false; //TEST timeline

var ul = div2.createAndAppendElement("ul") if( len > 0 ) { ul.appendChild( nekotb_fc.create_contrib_line(nekotb_fc.every_contrib_ordered[0]), false ) //car on ne peut pas comparer cette premiere ligne avec "celle d'avant" for(var a = 1; a < len ; a++ ) { var editClash = false //detecteur de clash if( nekotb_fc.every_contrib_ordered[a]['bgcolor'] != nekotb_fc.every_contrib_ordered[a-1]['bgcolor'] ) { var ecart = dat.setISO8601(nekotb_fc.every_contrib_ordered[a-1]['timestamp']).getTime - dat.setISO8601(nekotb_fc.every_contrib_ordered[a]['timestamp']).getTime / 1000 if( ecart < (nekotb_fc.edit_clash_limit * 60) ) { editClash = true }        }            ul.appendChild( nekotb_fc.create_contrib_line(nekotb_fc.every_contrib_ordered[a], editClash ) ) }  }   return false }

nekotb_fc.show_timeline = function(first_timestamp, last_timestamp) { var div2 = document.getElementById("nekotb_fc_fusion"); var dat = new Date; var final = new Array var nb_users = nekotb_fc.user_list.length

div2.createAndAppendElement("br") div2.createAndAppendTextNode("/!\\ Note : à cause des passages à l'heure d'été, le dernier dimanche de mars comporte 23 heures et le dernier dimanche d'octobre 25 heures")

//partie recuperation for( var a = 0 ; a < nb_users ; a++) { var cur_day = new Date(first_timestamp).getYYYYMMDD var user = nekotb_fc.user_list[a] final[user] = new Array var ligne = ''

var length_user_list = nekotb_fc.user_contribs[user].length

for(var b=0, next_timestamp=false, cur_timestamp=first_timestamp ; cur_timestamp <= last_timestamp ; cur_timestamp=next_timestamp) { next_timestamp = cur_timestamp + 1000 * 60 * 60 //1H plus tard var edit = 0 while( b < length_user_list && dat.setISO8601(nekotb_fc.user_contribs[user][b]['timestamp']).getTime < next_timestamp ) { edit++ b++ }        //caractere a afficher pour cette heure if(edit == 0) {        ligne += "_"   } else if (edit < 10) {  ligne += edit  } else {                 ligne += "X"   } var new_day = new Date(cur_timestamp).getYYYYMMDD //test changement de jour pour remplir une case if(cur_day != new_day) { final[user][cur_day] = new Array final[user][cur_day] = ligne ligne = '' cur_day = new_day }     } //boucle d'un user final[user][cur_day] = new Array final[user][cur_day] = ligne //mise du dernier jour } //boucle users

//partie affichage

var compteur_jours = 0; for(var jour = first_timestamp ; jour < last_timestamp ; jour += (1000 * 60 * 60 * 24) ) { if( compteur_jours % nekotb_fc.timeline_nb_day_per_line == 0 ) { var table = div2.createAndAppendElement("table", {"class":"nekotb_fc_timeline", "style":"font-family: monospace; border:1px solid black; margin-top:5px;"}) var tr = new Array; for(var a=0 ; a < nb_users+1 ; a++) { tr[a] = table.createAndAppendElement("tr") }         //premiere colonne : la liste des users tr[0].createAndAppendElement("td", {}, " ") for(var a=0 ; a < nb_users ; a++) { tr[a+1].createAndAppendElement("td", {}, nekotb_fc.user_list[a]) }      }

var cur_day = new Date(jour) tr[0].createAndAppendElement("td", {}, cur_day.getYYYYMMDD ) for(var b = 0 ; b < nekotb_fc.user_list.length ; b++) { tr[b+1].createAndAppendElement("td", {}, final[ nekotb_fc.user_list[b] ][ cur_day.getYYYYMMDD ] ) }

compteur_jours++ } }

nekotb_fc.create_contrib_line = function(tab, editClash) { var li = document.createElement("li") if( editClash == true) { //ligne rouge entre les 2 contribs incriminées li.setAttribute('style', "border-top: 5px solid red; background-color:"+tab['bgcolor']+";" ); } else { li.setAttribute('style', "background-color:"+tab['bgcolor']+";" ) }  var timestamp = tab['timestamp'].replace("T", " à ").replace("Z", "") //mise en page du timestamp

li.createAndAppendElement( "a", {'href':"/w/index.php?title="+tab['title']+"&oldid="+tab['revid']}, timestamp ) li.createAndAppendTextNode(" (")  li.createAndAppendElement( "a",  {'href':"/w/index.php?title="+tab['title']+"&oldid="+tab['revid']+"&diff=prev"}, "diff" )   li.createAndAppendTextNode(" | ")   li.createAndAppendElement( "a",  {'href':"/w/index.php?title="+tab['title']+"&action=history"}, "hist" )   li.createAndAppendTextNode(") ") if( tab['new'] != null ) { li.createAndAppendElement("span", {'class':"newpage"}, "N" ) }  if( tab['minor'] != null ) { li.createAndAppendElement("span", {'class':"minor"}, "m" ) }  li.createAndAppendTextNode(" ") li.createAndAppendElement( "a", {'href':"/wiki/"+tab['title']}, tab['title'] )

if( tab['comment'] != null && tab['comment'] != "" ) { li.createAndAppendTextNode(" (")     li.createAndAppendElement("span", {'class':"comment"}, tab['comment'] )      li.createAndAppendTextNode(")") }  if( tab['top'] != null ) { li.createAndAppendTextNode(" ") li.createAndAppendElement("span", {'class':"mw-uctop"}, "(dernière)" ) }  return li }

/* retourne un tableau a partir d'une (ou plusieurs) requetes API nekotb_fc.get_contribution_list = function(p_user) { var bgcolor = nekotb_fc.bgcolor_list[nekotb_fc.user_count++]  // choix de la couleur de fond

var ul = document.getElementById('nekotb_fc_fusion_user_list') // pour faire joujou avec l'affichage var li = ul.createAndAppendElement( "li", {'style':"background-color:"+bgcolor+";"}, " ")

var http_request = new XMLHttpRequest http_request.overrideMimeType('text/xml');

var tableau = new Array var compteur_tableau = 0 var newest_timestamp = nekotb_fc.newest_timestamp //variable locale car changé à chaque boucle var uccontinue = false do { if( tableau.length >= nekotb_fc.contrib_limit_per_user ) { li.firstChild.nodeValue += " Limite atteinte - les contributions les plus anciennes n'apparaitrons pas" break; }    var continue_do_while = false var address = "/w/api.php?format=xml&action=query&rawcontinue=&list=usercontribs&uclimit=500&ucuser=" + p_user + "&ucend=" + nekotb_fc.oldest_timestamp + "&ucstart=" + newest_timestamp

if( uccontinue ) { address += "&uccontinue="+uccontinue }

http_request.open('GET', address, false) http_request.send(null) var lignes = http_request.responseXML.documentElement.getElementsByTagName("item") for (var a = 0, len = lignes.length ; a < len ; a++) { tableau[compteur_tableau] = new Array for (var b = 0, len2 = lignes[a].attributes.length ; b < len2; b++) { tableau[compteur_tableau][ lignes[a].attributes.item(b).name ] = lignes[a].attributes.item(b).value }       tableau[compteur_tableau]['bgcolor'] = bgcolor //affectée ici pour ne pas avoir à reparser le tableau plus tard compteur_tableau ++ }

li.firstChild.nodeValue = p_user + " : " + tableau.length + " contributions récupérées."

query_continue = http_request.responseXML.documentElement.getElementsByTagName("query-continue") if( query_continue && query_continue.length > 0 ) { uccontinue = query_continue[0].getElementsByTagName("usercontribs")[0].getAttribute('uccontinue')

if( uccontinue && uccontinue != 'undefined' ) { //protection continue_do_while = true }    }   }  while( continue_do_while );

return tableau }