משתמש:Mikimik/js/partolStatusInEditList.js
מראה
הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.
- פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
- גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
- אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים 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 );