משתמש:Mikimik/js/partolStatusInEditList.js

מתוך ויקיפדיה, האנציקלופדיה החופשית

הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר / אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: ללחוץ על Ctrl-F5.
// the semaphore mechanism included here is not perfect, but the chance of a failure is negligible
// and will not necessarily break the script

// first action on the patrol system was on 05:25, 17 January 2007, revision 2494681
// earliest revision ever patrolled was revision 2471728 on 09:41, 19 January 2007

// in some cases a revision can show on the history page and not on the Recent Changes page:
//  1. the page was deleted and then undeleted - all revisions before the delete will not show
//  2. the page was moved and a redirect was created, later the redirect page was deleted - all revisions before the move will not show
//     if the page was moved without creating a redirect then the revisions will still show on the RC
//  3. a page was imported - all revisions before the import will not show

function initPartolStatusInEditList ( globallimit, list_type, node )
{
    var progressReportBox = null;
    var unknownRevCounter = 0, possibleMoveActionsCounter = 0, patrolledCounter = 0, unpatrolledCounter = 0;
    var revTable = [];                            // revTable[revision] = { anchor, time }
    var interestingPageNames = [];
    var timeDifference = null;                    // time difference between wiki UTC time (=the real UTC)
                                                  // and what the browser thinks is UTC time (could be wrong in some cases)
    var requestsDone = false, requestQueue = [];  // semaphore mechanism
    var unknownAbbr = null, patrolledAbbr = null, unpatrolledAbbr = null;
    var earliestRevTime = null,
        thirtyDaysAgoTime = new Date(new Date().getTime() - 30*24*60*60*1000);  // could be wrong because a possible time difference is ignored,
                                                                                // but a mistake is tolerable here
    var hebmonths = { "בינואר" : 0, "בפברואר" : 1, "במרץ" : 2, "באפריל" : 3, "במאי" : 4, "ביוני" : 5,
                      "ביולי" : 6, "באוגוסט" : 7, "בספטמבר" : 8, "באוקטובר" : 9, "בנובמבר" : 10, "בדצמבר" : 11 };

    function progressMsg ( msg, err )
    {
        progressReportBox.firstChild.nodeValue += ( msg == "." ? "" : /\.$/.test(progressReportBox.firstChild.nodeValue) ? " " : " ," ) + msg +
                                                  ( err ? ( " [" + ( err.message ? err.message : err ) + "]" ) : "" );
    }

    function wikiTextToTime ( text )
    {
        return new Date( /(\d\d):(\d\d), (\d\d?) ([^ \d]+) (\d{4})/.exec(text)[5],
                         hebmonths[/(\d\d):(\d\d), (\d\d?) ([^ \d]+) (\d{4})/.exec(text)[4]],
                         /(\d\d):(\d\d), (\d\d?) ([^ \d]+) (\d{4})/.exec(text)[3],
                         /(\d\d):(\d\d), (\d\d?) ([^ \d]+) (\d{4})/.exec(text)[1],
                         /(\d\d):(\d\d), (\d\d?) ([^ \d]+) (\d{4})/.exec(text)[2], 0, 0 );
    }

    function TimeToUTCTimestamp ( time, dir )
    {
        time = new Date( time.getTime() + (timeDifference ? timeDifference : 0) + (dir ? 60000 : 0) );
        return time.toUTCString().replace( /.* (\d\d) [a-z]+ (\d+) (\d\d):(\d\d):.*/i,
                                           "$2" + (time.getUTCMonth()+1).toString().replace(/^(\d)$/,"0$1") + "$1$3$400" );
    }

    function markRevisionStatus ( revid, status, rcid )
    {
        unknownRevCounter--;
        if ( status ) patrolledCounter++;
                 else unpatrolledCounter++;

        var newmarker = status ? patrolledAbbr.cloneNode(true) : unpatrolledAbbr.cloneNode(true);  // clone the appropriate marker
        revTable[revid].anchor.parentNode.replaceChild ( newmarker, revTable[revid].anchor );      // replace the revision's marker

        delete revTable[revid];                                                                    // remove the revision from the unknown list

        if ( rcid )
        {
            var allLinks = newmarker.parentNode.getElementsByTagName("A");       // a link with "oldid" must exist in the line,
            for ( var i = 0 ; !/&oldid=\d+/.test(allLinks[i].href) ; ) i++;      // it could be a diff link or the only revision in the history list
            allLinks[i].href += "&rcid=" + rcid;                                 // attach the rcid to the oldid link
        }

        return 1;
    }

    function requestRecentChanges ( jt )      // jt: { start, end, num }
    {
        var RCajaxObj = sajax_init_object();
        RCajaxObj.open ( "GET", wgServer + "/w/api.php?action=query&format=json&list=recentchanges" +
                                "&rcstart=" + TimeToUTCTimestamp(jt.start, true) + "&rcend=" + TimeToUTCTimestamp(jt.end, false) +
                                "&rcprop=ids|patrolled&rctype=edit|new&rclimit=500", true );
        RCajaxObj.onreadystatechange =
            function() {
                if ( RCajaxObj.readyState == 4 && RCajaxObj.status == 200 ) processRecentChangesResponse ( RCajaxObj.responseText, jt.num );
                 else progressMsg ( RCajaxObj.readyState == 4 ? "ERROR" : "." );
            };
        requestQueue.push ( requestQueue.length );
        RCajaxObj.send ( null );
        return 1;
    }

    function processRecentChangesResponse ( responseText, num )
    {
     try {
        var response = eval("("+responseText+")").query.recentchanges;
        if ( response.length == 500 ) progressMsg ("reached the 500 recentchanges limit!");     // never happens, hopefully

        var p = 0;

        for ( var i in response )
            if ( revTable[response[i].revid] )
                p += markRevisionStatus ( response[i].revid, response[i].patrolled === "", response[i].rcid );

        if ( num ) progressMsg ( "[" + p + "/" + num + "/" + response.length + "]" );

        if ( requestQueue.pop() ||                                  // terminate if open requests still exist
             !requestsDone         ) return;                        // or, if there will be more requests

//                               --- all recentchanges responses were processed! give final report and terminate script ---

        appendCSS ( "abbr .waiting { display:none; } abbr .donewaiting { display:inline; }" );
        progressMsg ( "DONE: " + patrolledCounter + " patrolled, " + unpatrolledCounter + " unpatrolled" +
                      ( unknownRevCounter ? " and " + unknownRevCounter + " unkonwns" : "" ) );
        if ( typeof(rcPatrol) == "function" ) rcPatrol();
      }
     catch (e)
      {
        progressMsg ( "ERROR in processRecentChangesResponse", e );
      }
    }

    function requestLogEvents ( title )
    {
        var logeventsAjaxObj = sajax_init_object();
        logeventsAjaxObj.open ( "GET", wgServer + "/w/api.php?action=query&format=json&list=logevents&letype=patrol&letitle=" +
                                       encodeURIComponent(title).replace(/%2F/g,"/").replace(/%24/g,"$").replace(/%2C/g,",")
                                                                .replace(/%3A/g,":").replace(/%40/g,"@") +
                                       "&lestart=" + earliestRevTime + "&ledir=newer&leprop=ids|details&lelimit=" + globallimit, true );
        logeventsAjaxObj.onreadystatechange =
            function() {
                if ( logeventsAjaxObj.readyState == 4 && logeventsAjaxObj.status == 200 ) processLogEventsResponse ( logeventsAjaxObj.responseText );
                 else progressMsg ( logeventsAjaxObj.readyState == 4 ? "ERROR" : "." );
            };
        requestQueue.push ( requestQueue.length );
        logeventsAjaxObj.send ( null );
        return 1;
    }

    function processLogEventsResponse ( responseText, flag )
    {
     try {
        var response = eval("("+responseText+")").query.logevents;  // patrol log actions may be in different order than the creation time of
                                                                    // the revisions, and also include deleted revisions
                                                                    // thus, the log could have more or less revisions than the displayed history list
        var p = 0;

        for ( var i in response )
            if ( revTable[response[i].patrol.cur] )
                p += markRevisionStatus ( response[i].patrol.cur, true );   // mark revision as patrolled

        if ( !flag ) progressMsg ( "[" + p + "/" + response.length + "]" );

        if ( requestQueue.pop() ||                                  // terminate if open requests still exist
             !requestsDone ||                                       // or, if there will be more requests
             timeDifference === null ) return;                      // or, if the time difference is not yet known

//            --- all logevents responses were processed and the time difference is known! now ask for recentchanges lists ---
        var req = 0;
        var joinedTimeTable = [];
        var endtime = null, previoustime = null, num = 0;
        requestsDone = false;

        for ( var i in revTable )
        {
            if ( !num ) endtime = revTable[i].time;
             else
                if ( revTable[i].time - endtime >= 30*60*1000 )       // get a recent changes list that is not more than 30 minutes long,
                {                                                     // the assumption is that a 30 minutes recent changes list on
                                                                      // hebrew wikipedia is nowhere near the 500 changes limit
                    joinedTimeTable.push ( { "start": previoustime, "end": endtime, "num": num } );
                    endtime = revTable[i].time;
                    num = 0;
                }

            previoustime = revTable[i].time;
            num++;
        }

        if ( num ) joinedTimeTable.push ( { "start": previoustime, "end": endtime, "num": num } );

        joinedTimeTable.reverse();
        for ( var i in joinedTimeTable )
            if ( req <= 50 ) req += requestRecentChanges ( joinedTimeTable[i] );
             else break;

        if ( req ) progressMsg ( "waiting for " + req + " recentchanges responses" );
        requestQueue.push ( requestQueue.length );
        requestsDone = true;
        processRecentChangesResponse ( '{"query":{"recentchanges":[]}}', 0 );
      }
     catch (e)
      {
        progressMsg ( "ERROR in processLogEventsResponse", e );
      }
    }

    function processRevTimeResponse ( responseText, displayedTimestamp )
    {
     try {
        var response = /"timestamp":"(.+?)"/.exec(responseText)[1];

        timeDifference = ( new Date( /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):/.exec(response)[1],
                                     /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):/.exec(response)[2] - 1,
                                     /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):/.exec(response)[3],
                                     /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):/.exec(response)[4],
                                     /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):/.exec(response)[5], 0, 0 ) -
                           new Date( /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)/.exec(displayedTimestamp)[1],
                                     /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)/.exec(displayedTimestamp)[2] - 1,
                                     /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)/.exec(displayedTimestamp)[3],
                                     /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)/.exec(displayedTimestamp)[4],
                                     /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)/.exec(displayedTimestamp)[5], 0, 0 ) );

        progressMsg ( "time difference is " + timeDifference/3600000 + " hours" );

        processLogEventsResponse ( '{"query":{"logevents":[]}}', true );
      }
     catch (e)
      {
        progressMsg ( "ERROR in processRevTimeResponse", e );
      }
    }


 try {
    if ( !( wgServer === "http://he.wikipedia.org" || wgServer === "http://he.wikiquote.org" ) || false ||
         !/\b(sysop|patroller)\b/.test(wgUserGroups.join(" ")) || wgNamespaceNumber === 8                         ) return;

    if ( !node ) var node = document.getElementById("bodyContent");

    if ( wgAction === "history" || list_type === "history" )
    {
        list_type = "history";
        for ( var ul = node.getElementsByTagName("ul"), i = 0 ; i < ul.length ; i++ )
            if ( ul[i].id === "pagehistory" )
            {
                var lines = ul[i].getElementsByTagName("LI");
                break;
            }
    }
     else if ( ( wgCanonicalSpecialPageName === "Contributions" &&
                 decodeURIComponent(window.location.pathname.replace(/^\/wiki\/[^:/]+:[^:/]+\//,"")) !== wgUserName ) ||
               ( list_type === "contributions" &&
                 decodeURIComponent(window.location.pathname.replace(/^\/wiki\/[^:/]+:/,""))         !== wgUserName )    )
    {
        list_type = "contributions";
        if ( node.getElementsByTagName("ul") && node.getElementsByTagName("ul")[0] )
            var lines = node.getElementsByTagName("ul")[0].getElementsByTagName("LI");
    }

    if ( !lines || !lines[0] || !document.getElementById("firstHeading") ) return;

    if ( !globallimit ) globallimit = 500;

    unknownAbbr = document.createElement("ABBR");                      // <span class="waiting">/</span><span class="donewaiting">?<span>
    unknownAbbr.className = "unknown";
    unknownAbbr.appendChild ( document.createElement("SPAN") );
    unknownAbbr.firstChild.appendChild ( document.createTextNode("/") );
    unknownAbbr.firstChild.className = "waiting";
    unknownAbbr.firstChild.title = "ממתין לתשובה מהשרת";
    unknownAbbr.appendChild ( document.createElement("SPAN") );
    unknownAbbr.lastChild.appendChild ( document.createTextNode("?") );
    unknownAbbr.lastChild.className = "donewaiting";
    unknownAbbr.lastChild.title = "לא ידוע";
    patrolledAbbr = document.createElement("ABBR");
    patrolledAbbr.className = "patrolled";
    patrolledAbbr.appendChild ( document.createTextNode("+") );
    patrolledAbbr.title = "עריכה זו נבדקה";
    unpatrolledAbbr = document.createElement("ABBR");
    unpatrolledAbbr.className = "unpatrolled";
    unpatrolledAbbr.appendChild ( document.createTextNode("!") );
    unpatrolledAbbr.title = "עריכה זו טרם נבדקה";
    appendCSS ( "abbr { border-bottom:none; } abbr .waiting { color:blue; } abbr .donewaiting { color:purple; display:none; } abbr.patrolled { color:green; }" );

    progressReportBox = document.createElement("div");
    progressReportBox.style.cssText = "direction:ltr; height:75px; background-color:#F9F9F9; border:1px solid #AAAAAA; padding:2px; font-size:small; overflow:auto;" +
                                      ( window.showPartolStatusInEditListUserMsg ? "" : "display:none;" );
    progressReportBox.appendChild ( document.createTextNode(lines.length + " revisions on page") );
    document.getElementById("firstHeading").parentNode.insertBefore ( progressReportBox, document.getElementById("firstHeading").nextSibling );

// ---                             --- collect revisions oldid and time, place unknown marker and check for possible move actions ---
    var index = Math.min( lines.length, globallimit + 1 );
    var lasttime = null, lastsize = null, size = -1;

    do {                                                                     // find the oldest revision that's newer than thirty days
        index--;
        var x = lines[index].firstChild;
        while ( x && !/&oldid=\d+/.test(x.href || "") ) x = x.nextSibling;   // find oldid (if the content is hidden there's no oldid)
        if ( x ) lasttime = wikiTextToTime( x.firstChild.nodeValue );        // get revision time (it's not a full timestamp)
    } while ( index && ( !lasttime || lasttime < thirtyDaysAgoTime ) );

    if ( !lasttime ) return progressMsg ( "ABORT: revisions are all hidden" );
    if ( lasttime < thirtyDaysAgoTime ) return progressMsg ( "ABORT: revisions are too old" );

// ---                                                            --- find the time difference ---
    var revTimeAjaxObj = sajax_init_object();
    revTimeAjaxObj.open ( "GET", wgServer + "/w/api.php?action=query&format=json&prop=revisions" +
                                 "&revids=" + /&oldid=(\d+)/.exec(x.href)[1] + "&rvprop=timestamp", true );
    revTimeAjaxObj.onreadystatechange =
        function() {
            if ( revTimeAjaxObj.readyState == 4 && revTimeAjaxObj.status == 200 )
                processRevTimeResponse ( revTimeAjaxObj.responseText, TimeToUTCTimestamp(lasttime) );
             else progressMsg ( revTimeAjaxObj.readyState == 4 ? "ERROR" : "." );
        };
    progressMsg ( "what's the time?" );
    requestQueue.push ( requestQueue.length );
    revTimeAjaxObj.send ( null );

//---
    earliestRevTime = TimeToUTCTimestamp ( new Date(lasttime.getTime()-3600000) );  // add one hour, just in case there is a time difference
    if ( wgNamespaceNumber >= 0 ) interestingPageNames[wgPageName.replace(/_/g," ")] = true;

    for ( ; index >= 0 ; index-- )
    {
        lastsize = size;
        size = -1;

        var x = lines[index].firstChild;
        while ( x && !/&oldid=\d+/.test(x.href || "") ) x = x.nextSibling;   // find oldid (if the content is hidden there's no oldid)
        if ( !x ) continue;

        unknownRevCounter++;

        var oldid = /&oldid=(\d+)/.exec(x.href)[1];
        revTable[oldid] = {};
        revTable[oldid].time = wikiTextToTime(x.firstChild.nodeValue);       // keep revision time (it's not a full timestamp)
        revTable[oldid].anchor = unknownAbbr.cloneNode(true);                // clone unknown marker
        x.parentNode.insertBefore ( revTable[oldid].anchor, x );             // insert the marker before the oldid link
        x.parentNode.insertBefore ( document.createTextNode(" "), x );

        if ( list_type !== 'history' ) continue;

        while ( x.className != "history-size" ) x = x.nextSibling;           // find revision size (it must exist when an oldid exists)
        size = /[0-9]/.test(x.firstChild.nodeValue) ? x.firstChild.nodeValue.replace(/[^0-9]/g,"") : 0;
        if ( lastsize != -1 && size != lastsize ) continue;                   // revision can't be a move action if the size difference is not zero

        while ( x && x.className != "comment" ) x = x.nextSibling;            // find comment (comment could be hidden)
        if ( x && x.childNodes.length >= 5 &&                                 // identify a comment structure that looks like a move action
             x.childNodes[0].nodeName == "#text" && x.childNodes[0].nodeValue == "(" &&
             x.childNodes[1].nodeName == "A" &&
             x.childNodes[2].nodeName == "#text" && x.childNodes[2].nodeValue == " הועבר ל" &&
             x.childNodes[3].nodeName == "A" &&
             x.childNodes[4].nodeName == "#text" && /^( במקום הפניה)?[:)]/.test(x.childNodes[4].nodeValue) )
        {                                               // there's no 100% certainty, but mistakes are rare and can be tolerated
            interestingPageNames[x.childNodes[1].firstChild.nodeValue] = true;
            interestingPageNames[x.childNodes[3].firstChild.nodeValue] = true;
            possibleMoveActionsCounter++;
        }
    }

    progressMsg ( unknownRevCounter + " relevant revisions" + 
                  ( possibleMoveActionsCounter ? " (" + possibleMoveActionsCounter + " possible move actions)" : "" ) );

//                                                  --- request logevents for interesting pagenames ---
    var req = 0;
    for ( var pagename in interestingPageNames ) req += requestLogEvents ( pagename );

    if ( req ) progressMsg ( "waiting for " + req + " logevents responses" );

//---
    requestQueue.push ( requestQueue.length );
    requestsDone = true;
    processLogEventsResponse ( '{"query":{"logevents":[]}}', true );
  }
 catch(e)
  {
    progressMsg ( "ERROR in initPartolStatusInEditList", e );
  }
}

if ( wgAction === "history" || wgCanonicalSpecialPageName === "Contributions" ) addOnloadHook ( initPartolStatusInEditList );