Jump to content

User:SoledadKabocha/linkclassifier.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by SoledadKabocha (talk | contribs) at 07:21, 10 September 2016 (bump the probability as I will begin work within a day). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* Modified from [[User:Anomie/linkclassifier.js]] - THIS IS NO LONGER BEING UPDATED WITH NEW FEATURES ADDED BY Anomie.
PLEASE CHECK THE DOCUMENTATION PAGE FOR NEWS/INFO. A new version will be at a different page title.
Once the new fork is stable, this one will be "redirected" there via an importScript call.
If you want to use this script, simply add the following line to your [[Special:MyPage/common.js]] or [[Special:MyPage/skin.js]]:

importScript('User:Anomie/linkclassifier.js'); //Linkback: [[User:SoledadKabocha/linkclassifier.js]]
 
(Please keep the comment so I can see how many people use this)
[^ XXX: I don't actually care; that was copied from Anomie].
You will also want to add some CSS rules, such as those at [[User:Anomie/linkclassifier.css]].
Note that the classes newly added to this script do not have rules in Anomie's stylesheet.
See the documentation page for a complete list of such classes.
 */
window.LinkClassifierSupportsFuncChain = true; // flag to distinguish this script from the original linkclassifier; SHOULD be read but not modified by other scripts

// These need to be global so other scripts (e.g. jsMessage) can read them (XXX: really?)
var LinkClassifierApiRequestsOutstanding = 0;
var LinkClassifierChainedFuncCalledAlready = false;

function defaultPref( cfig, dflt ) {
  return ( typeof cfig == typeof dflt ? cfig : dflt );
}

window.LinkClassifierErrMsgPrefix = defaultPref( window.LinkClassifierErrMsgPrefix, '<span class="linkclassifier-error">LinkClassifier FAILED: Something went wrong with the server or your Internet connection, or you impatiently browsed to another page.</span><br />' );

var LinkClassifier={
    /* This object maps classes to the categories for which to apply them. Values may be an array of strings or a regex. */
    cats:{
        deletion:[
            'Category:All articles proposed for deletion',
            'Category:All books proposed for deletion',
            'Category:All disputed non-free Wikipedia files',
            'Category:All orphaned non-free use Wikipedia files',
            'Category:All possibly unfree Wikipedia files',
            'Category:All replaceable non-free use Wikipedia files',
            'Category:All Wikipedia files with no copyright tag',
            'Category:All Wikipedia files with no non-free use rationale',
            'Category:All Wikipedia files with unknown copyright status',
            'Category:All Wikipedia files with unknown source',
            'Category:Articles for deletion',
            'Category:Articles on deletion review',
            'Category:Candidates for speedy deletion',
            'Category:Candidates for undeletion',
            'Category:Categories for conversion',
            'Category:Categories for deletion',
            'Category:Categories for listifying',
            'Category:Categories for merging',
            'Category:Categories for renaming',
            'Category:Categories for speedy renaming',
            'Category:Categories to be listified then deleted',
            'Category:Duplicate or hardcoded templates awaiting deletion',
            'Category:Items pending OTRS confirmation of permission for over 30 days',
            'Category:Miscellaneous pages for deletion',
            'Category:Stub categories for deletion',
            'Category:Stub template deletion candidates',
            'Category:Templates for deletion',
            'Category:Wikipedia deprecated and orphaned templates',
            'Category:Wikipedia files for deletion',
            'Category:Wikipedia files with unknown source for deletion in dispute',
            'Category:Wikipedia templates for deletion'
        ].sort(),
        'deletion-rfd':[
            'Category:Redirects for discussion', // moved from deletion - obsolete?
            'Category:All redirects for discussion'
        ].sort(),
        disambiguation:[
            'Category:All disambiguation pages'
        ].sort(),
        'set-index':[
            'Category:All set index articles'
        ].sort(),
        'featured-content':[
            'Category:Featured articles',
            'Category:Featured lists',
            'Category:Featured pictures',
            'Category:Featured sounds',
            'Category:Featured videos',
            'Category:Featured portals'
        ].sort(),
        'good-content':[
            'Category:Good articles'
        ].sort(),
        'soft-redirect':[
            'Category:Wikipedia soft redirects',
            'Category:Wikipedia interwiki soft redirects',
            'Category:Wikipedia soft redirected project pages',
            'Category:Protected soft redirects',
            'Category:Wikipedia soft redirected talk pages',
            'Category:Wikipedia soft redirected templates',
            'Category:User soft redirects',
            'Category:Redirects to Wikiquote',
            'Category:Deprecated shortcuts',
            'Category:Redirects to Wiktionary'
        ].sort(),
        'soft-redirect-cats':[
            'Category:Wikipedia soft redirected categories',
        ].sort(),
        'spoken-articles':[
            'Category:Spoken articles'
        ].sort(),
        stubcls:/^Category:.* stubs$/,
        'nonfree-media':[
            'Category:All non-free media'
        ].sort(),
        'no-attrib-reqd-media':[
            //'Category:Cc-zero images',
            'Category:CC-zero files',
            //'Category:All user-created public domain images',
            'Category:All User-created public domain files',
            //'Category:Author died more than 100 years ago public domain images',
            'Category:Author died more than 100 years ago public domain files',
            //'Category:Author died more than 70 years ago public domain images',
            'Category:Author died more than 70 years ago public domain files',
            //'Category:Copyright holder released public domain images',
            'Category:Copyright holder released public domain files',
            'Category:PD OpenClipart',
            'Category:PD US no notice',
            'Category:PD US not renewed',
            'Category:PD chem',
            'Category:PD other reasons',
            'Category:PD script',
            'Category:PD tag needs updating',
            'Category:PD-link',
            'Category:Pre 1978 without copyright notice US public domain images', // still current name
            'Category:Public domain art',
            //'Category:Public domain images',
            'Category:Public domain files',
            //'Category:Public domain images ineligible for copyright',
            'Category:Public domain files ineligible for copyright',
            'Category:Public domain images of currency', // still current name
            'Category:Public domain images of fonts', // still current name
            'Category:Public domain music images', // still current name
            'Category:Files copyrighted by the Wikimedia Foundation'
        ].sort(),
        unprintworthy:[
            'Category:Unprintworthy redirects',
            'Category:Middle-earth redirects from redundant titles'
        ].sort(),
        'unprintworthy-shortcut':[
            'Category:Redirects from shortcuts'
        ].sort(),
        printworthy:[
            'Category:Printworthy redirects',
            'Category:Redirects from systematic names',
            'Category:Redirects from systematic abbreviations',
            'Category:Redirects from ISO 3166 codes',
            'Category:Redirects from postal abbreviations',
            'Category:Redirects from scientific abbreviations',
            'Category:Redirects from stock symbols',
            'Category:Redirects from scientific names'
        ].sort(),
        invalidisbn:[
            'Category:Articles with invalid ISBNs',
            'Category:Articles with invalid ISSNs',
            'Category:Pages with ISBN errors' // applied by Citation Style 1 module, so also in cs1luaerror
        ].sort(),
        missingisbn:[
            'Category:Articles lacking ISBNs'
        ].sort(),
        'blp-sources':/^Category:BLP articles lacking sources( from |$)/,
        'blp-unreferenced':[
            'Category:All unreferenced BLPs',
        ].sort(),
        'blp-sourceimdb':/^Category:Articles sourced (only )?by IMDb( from |$)/,
        'unclear-notability':[
            'Category:All articles with topics of unclear notability'
        ].sort(),
        'need-coord':[
            'Category:All articles needing coordinates',
            'Category:Pages with malformed coordinate tags'
        ].sort(),
        'cleanup-translation':[
            'Category:Wikipedia articles needing cleanup after translation'
        ].sort(),
        deadend:[
            'Category:All dead-end pages',
            'Category:All articles with too few wikilinks',
            'Category:All articles covered by WikiProject Wikify'
        ].sort(),
        'expert-attention':[
            'Category:All articles needing expert attention'
        ].sort(),
        'user-special-block':[
            'Category:Wikipedia contact role accounts',
            'Category:Wikipedia maintenance scripts'
        ].sort(),
        'user-compromised-block':[ // not 100% useful because the user(talk) pages may not exist
            'Category:Compromised accounts'
        ].sort(),
        'user-altaccount':[
            'Category:Wikipedia doppelganger accounts'
        ].sort(),
        'redirect-possibilities':[
            'Category:Redirects with possibilities',
            'Category:Redirects from albums',
            'Category:Redirects from books',
            'Category:Redirects from brand names',
            'Category:Redirects from EPs',
            'Category:Redirects from films',
            'Category:Redirects from former names',
            'Category:Redirects to list entries',
            //'Category:Redirects from people', // doesn't currently contain articles
            'Category:Redirects from phrases',
            'Category:Redirects from products',
            'Category:Redirects from school articles',
            'Category:Redirects from songs',
            'Category:Redirects from writers',
            //'Category:Redirects from transport routes', // doesn't currently contain articles
            'Category:Comics redirects with possibilities',
            'Category:Redirects to decade',
            'Category:Middle-earth redirects with possibilities',
            'Category:Redirects from birth names',
            'Category:Redirects from alternative characters',
            'Category:Comics redirects to lists',
            'Category:Comics redirects from related words',
            'Category:Comics redirects to sections',
            'Category:Middle-earth redirects to lists',
            'Category:Redirects from birth names',
            'Category:Redirects from historic names',
            'Category:Redirects from maiden names',
            'Category:Redirects from married names',
            'Category:Middle-earth redirects from former names',
            'Category:Characters redirects to lists',
            'Category:Episode redirects to lists', // XXX: Subcats not done!
            'Category:Redirected episode articles',
            'Category:Fiction-based redirects to list entries',
            'Category:Redirected characters articles',
            'Category:Aqua Teen Hunger Force characters redirects to lists',
            'Category:Courage the Cowardly Dog characters redirects to lists',
            'Category:I Am Weasel characters redirects',
            'Category:SpongeBob SquarePants characters redirects to lists',
            'Category:The Venture Bros. characters redirects to lists',
            'Category:Fictional character redirects to lists', // XXX: Subcats not done!
            //'Category:Fictional element redirects to lists', // doesn't currently contain articles
            'Category:Redirected fictional element articles',
            'Category:Redirects from individual people',
            'Category:Redirects from multiple people',
            'Category:Redirects from members',
            'Category:Redirects from quotations',
            'Category:Redirects from slogans',
            'Category:Redirects from highway routes',
            'Category:Redirects from London bus routes',
            'Category:Redirects from New York City area bus routes',
            'Category:Redirects from railroad names with ampersands',
            'Category:Redirects from highway in region' // has a "without possibilities" subcat...
        ].sort(),
        'redirect-section':[                  // Anomie said not 100% useful because a human has to manually add category
            'Category:Redirects to sections',
            'Category:Redirects to embedded anchors'
        ].sort(),
        'redirect-listentry':[
            'Category:Redirects to list entries', // also in -possibilities, I know
            'Category:Redirects from list topics'
        ].sort(),
        'redirect-incompletedab':[
            'Category:Redirects from incomplete disambiguations'
        ].sort(),
        chemobot:/^Category:(\w+boxes which contain changes to (watched|verified) fields|Articles (containing unverified chemical infoboxes|with changed (\w+ identifier|CASNo)))$/,
        citeerror:/^Category:(Pages with (\w+ reference names|incorrect ref formatting|missing references list|reference errors)|Articles with incorrect citation syntax)$/,
        npovdispute:[
            'Category:All NPOV disputes'
        ].sort(),
        accuracydispute:[
            'Category:All accuracy disputes'
        ].sort(),
        copyedit:[
            'Category:All articles needing copy edit'
        ].sort(),
        deadextlink:[
            'Category:All articles with dead external links',
            'Category:All articles with broken or outdated citations'
        ].sort(),
        outofdate:/^Category:(Articles with obsolete information|Wikipedia articles in need of updating)( from |$)/,
        nofootnotes:[
            'Category:All articles lacking in-text citations'
        ].sort(),
        'disambiguation-cleanup':/^Category:(Disambiguation pages (in need of (being split|cleanup)|to be converted to broad concept articles)|Incomplete disambiguation)( from |$)/,
        'redirect-crossnamespace':[
            'Category:Cross-namespace redirects',
            'Category:Redirects to category space',
            'Category:Redirects to help namespace',
            'Category:Redirects to the main namespace',
            'Category:Redirects to portal space',
            'Category:Redirects to template from non-template namespace',
            'Category:Redirects to project space',
            'Category:Redirects to user namespace',
            'Category:Redirects to talk pages'
        ].sort(),
        'systemicbias-geo':/^Category:((Articles with (disproportional|limited)|Vague or ambiguous) geographic scope( from |$)|.+-centric$)/,
        'systemicbias-time':/^Category:Articles (slanted towards recent events( from |$)|lacking historical information$)/,
        'systemicbias-other':/^Category:Articles needing more viewpoints( from |$)/,
        tempundelete:[
            'Category:Candidates for undeletion'
        ].sort(),
        'promotional-tone':[
            'Category:All articles with a promotional tone'
        ].sort(),
        'retracted-pub':[
            'Category:Articles citing retracted publications'
        ].sort(),
        rewrite:/^Category:Wikipedia articles needing rewrite( from |$)/,
        subscriptiononly:[
            'Category:Pages containing links to subscription only content'
        ].sort(),
        povcheck:/^Category:Articles needing POV-check( from |$)/,
        cs1luaerror:/^Category:(Pages (using (web )?citations with|with archiveurl cit.+ errors$|with citations ((hav|us)ing|lacking titles$)|with [A-Z]+ errors$|with empty citations$)|CS1 (errors|maint))/,
        'transwiki-cleanup':[
            'Category:Transwiki cleanup'
        ].sort(),
        'template-error':[
            'Category:Pages containing omitted template arguments',
            'Category:Pages where expansion depth is exceeded',
            'Category:Pages where node count is exceeded',
            //'Category:Pages where node-count is exceeded', //no longer used in current MW versions (which includes all WMF wikis)
            'Category:Pages where template include size is exceeded',
            'Category:Template loop warnings',
            'UserLinks transclusions with errors' //XXX: either separate this from the non-Wikipedia-specific stuff above, or generalize it all into a regex
        ].sort(),
        'disambiguation-cats':[
            'Category:Disambiguation categories'
        ].sort(),
        luaerror:[
            'Category:Pages with script errors'
        ].sort(),
        'redirect-grammaticalnumber':/^Category:Redirects (from|to) plurals$/
    },

    /* This object maps page props to CSS classes for which to apply them. Values may be an array of strings or a function returning such. */
    props: {
        disambiguation:[
            'disambiguation'
        ],
    },

    /* This regex matches page titles to be marked as intentional links to disambiguation pages */
    intentionaldab: / \(disambiguation\)$/,

    /* Was it run already? */
    wasRun: false,

    /* Track whether we have already sent any AJAX requests on the current run, so the slowdown feature can avoid delaying the first request */
    isFirstRequest: true,

    initUnloadHandlerIfNeeded:function( ) {
        if ( window.LinkClassifierUninterruptible === true ) {
            if ( ( wgAction == 'edit' || wgAction == 'submit' ) && $.compareObject( mw.user.options.get( [ 'skin' ] ), { skin: 'vector' } ) ) {
                mw.util.jsMessage( 'linkclassifier: Ignoring user setting of uninterruptible mode to avoid spurious warnings saving edits in the Vector skin' );
                window.LinkClassifierUninterruptible = false; // still needed?
                return;
            }
            else {
                window.LinkClassifierOldOnbeforeunload = window.onbeforeunload;
                window.onbeforeunload = function (evt) {
                    var message = 'LinkClassifier is still waiting for API responses.';
                                  //XXX: At least Firefox no longer shows the custom msg...
                    if (evt === undefined) {
                        evt = window.event;
                    }
                    if (evt) {
                        evt.returnValue = message;
                    }
                    return message;
                }
            }
            // need to check whether the timeout already exists?
            window.LinkClassifierUnloadSafetyTimeout = setTimeout( LinkClassifier.removeUnloadHandlerIfNeeded, 42000 );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                $( window.LinkClassifierStatusSel ).addClass( 'linkclassifier-unload-added' );
            }
        }
    },

    removeUnloadHandlerIfNeeded:function( ) {
        if ( !window.LinkClassifierUninterruptible || window.LinkClassifierUnloadHandlerRemoved ) return;

        window.onbeforeunload = window.LinkClassifierOldOnbeforeunload; // does this need a type safety check?
        if ( typeof window.LinkClassifierUnloadSafetyTimeout == 'number' && window.LinkClassifierUnloadSafetyTimeout != 0 ) {
            clearTimeout( window.LinkClassifierUnloadSafetyTimeout );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-unload-added' );
                statusElement.addClass(    'linkclassifier-unload-removed' );
            }
        }

        window.LinkClassifierUnloadHandlerRemoved = true;
    },

    forceCallChainedFunc:function( ) {
        // XXX: add (temporary/optional?) debug prints to all early exit conditions that call this
        if ( !LinkClassifierChainedFuncCalledAlready && typeof( window.LinkClassifierChainedFunc ) === 'function' ) {
            LinkClassifierChainedFuncCalledAlready = true;
            if ( window.LinkClassifierChainedFuncArg === undefined ) {
                LinkClassifierChainedFunc();
            }
            else {
                LinkClassifierChainedFunc( window.LinkClassifierChainedFuncArg );
            }
        }
        LinkClassifier.removeUnloadHandlerIfNeeded( );

        if ( typeof window.LinkClassifierStatusSel == 'string' ) {
            var statusElement = $( window.LinkClassifierStatusSel );
            statusElement.removeClass( 'linkclassifier-started' );
            statusElement.addClass(    'linkclassifier-finished' );
        }
    },

    // should be renamed "finalizeRequest" or similar
    maybeCallChainedFunc:function(xhr, sts){
        if ( LinkClassifierApiRequestsOutstanding <= 0 ) {
            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            if ( window.console !== undefined && typeof(window.console.error)=='function' ) {
                // XXX: should the message include wasRun?
                window.console.error( 'linkclassifier: Finished more API requests than started (maybe multiple copies are running)?! (curr=' + LinkClassifierApiRequestsOutstanding + ')' );
            }
            mw.util.jsMessage( 'linkclassifier: Finished more API requests than started<br />(maybe multiple copies are running)?!<br />(curr=' + LinkClassifierApiRequestsOutstanding + ')' );
        }
        else --LinkClassifierApiRequestsOutstanding;
        if ( LinkClassifierApiRequestsOutstanding <= 0 ) {
            LinkClassifier.forceCallChainedFunc( );
        }
    },

    makeAjaxRequest:function(dat, errLoc) {
        //XXX should the increment of LinkClassifierApiRequestsOutstanding be done here rather than in the caller?

        $.ajax({
            url:mw.util.wikiScript('api'),
            dataType:'json',
            type:'POST',
            data:dat,
            rawdata:dat,
            success:LinkClassifier.callback,
            error:function(xhr,textStatus,errorThrown){
                LinkClassifier.removeUnloadHandlerIfNeeded( );

                if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                    var statusElement = $( window.LinkClassifierStatusSel );
                    statusElement.removeClass( 'linkclassifier-started' );
                    statusElement.addClass(    'linkclassifier-aborted' );
                }

                var errTxt = 'AJAX error ('+errLoc+'), with '
                           + LinkClassifierApiRequestsOutstanding + ' requests in progress: '+textStatus+' '+errorThrown;
                mw.util.jsMessage( window.LinkClassifierErrMsgPrefix + errTxt );
                throw new Error( errTxt );
            },
            complete:LinkClassifier.maybeCallChainedFunc
        });

        LinkClassifier.isFirstRequest = false;
    },

    //create a timeout that executes in the scope of object LinkClassifier
    //adapted from https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#A_possible_solution
    lcSetTimeout:function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
      var __nativeST__ = window.setTimeout, oThis = this, aArgs = Array.prototype.slice.call(arguments, 2);
      return __nativeST__(vCallback instanceof Function ? function () {
        vCallback.apply(oThis, aArgs);
      } : vCallback, nDelay);
    },

    makeAjaxRequestWithDelay:function(dat, errLoc) {
        var slowdownValid = ( !LinkClassifier.isFirstRequest ) &&
                            ( typeof window.LinkClassifierSlowDownMin == 'number' ) &&
                            ( window.LinkClassifierSlowDownMin == Math.ceil( window.LinkClassifierSlowDownMin ) ) &&
                            ( window.LinkClassifierSlowDownMin >= 0 ) &&
                            ( typeof window.LinkClassifierSlowDownMax == 'number' ) &&
                            ( window.LinkClassifierSlowDownMax == Math.ceil( window.LinkClassifierSlowDownMax ) ) &&
                            ( window.LinkClassifierSlowDownMax > 0 ) &&
                            ( window.LinkClassifierSlowDownMax >= window.LinkClassifierSlowDownMin );

        if ( !slowdownValid ) {
            LinkClassifier.makeAjaxRequest( dat, errLoc );
        }
        else {
            var realDelay = window.LinkClassifierSlowDownMin;
            if ( realDelay != window.LinkClassifierSlowDownMax ) {
                realDelay += Math.floor( Math.random( ) * ( window.LinkClassifierSlowDownMax - window.LinkClassifierSlowDownMin + 1 ) );
            }

            // Minimum timeout honored by browser is 4ms; if we chose less, do some rounding
            // https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#Nested_timeouts_forced_to_%3E4ms
            if ( realDelay <= 1 ) {
                realDelay = 0;
            }
            else if ( realDelay <= 3 ) {
                realDelay = 4;
            }

            if ( realDelay == 0 ) {
                LinkClassifier.makeAjaxRequest( dat, errLoc );
            }
            else {
                LinkClassifier.lcSetTimeout( LinkClassifier.makeAjaxRequest, realDelay, dat, errLoc );
            }
        }
    },

    callback:function(r, sts, xhr){
        if(!r.query) {
            LinkClassifier.removeUnloadHandlerIfNeeded( );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            mw.util.jsMessage( 'linkclassifier: Bad response; ' + LinkClassifierApiRequestsOutstanding + ' requests were in progress' );
            if( window.console === undefined || typeof(window.console.error)!='function')
                throw new Error('Bad response; ' + LinkClassifierApiRequestsOutstanding + ' requests were in progress');
            window.console.error("Bad response; " + LinkClassifierApiRequestsOutstanding + ' requests were in progress', r);
            return;
        }
        if(r['query-continue']){
            var cc=this.rawdata;
            for(var k in r['query-continue']){
                for(var k2 in r['query-continue'][k]){
                    cc[k2]=r['query-continue'][k][k2];
                }
            }
            LinkClassifierApiRequestsOutstanding++;
            LinkClassifier.makeAjaxRequestWithDelay( cc, 'callback query-continue' );
        }
        r=r.query;

        var a=document.getElementById('wikiPreview');
        if(!a && $('.mw-redirectedfrom').length == 1) a=document.getElementById('mw-content-text');
        if(!a) a=document.getElementById('bodyContent');
        if(!a) {
            LinkClassifier.removeUnloadHandlerIfNeeded( );

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                var statusElement = $( window.LinkClassifierStatusSel );
                statusElement.removeClass( 'linkclassifier-started' );
                statusElement.addClass(    'linkclassifier-aborted' );
            }

            throw new Error('Huh? No body content?');
        }
        a=a.getElementsByTagName('A');
        if(!a || a.length==0) { LinkClassifier.forceCallChainedFunc( ); return; }

        var redir={};
        var redirlist=[];
        if(r.redirects) for(var i=r.redirects.length-1; i>=0; i--){
            redir[r.redirects[i].from]=r.redirects[i].to;
            redirlist.push(r.redirects[i].from);
        }
        if(redirlist.length>0) {
            var q = {
                format:'json',
                action:'query',
                titles:redirlist.join('|'),
                prop:'categories|info',
                cllimit:'max',
                inprop:'protection',
                rawcontinue:1
            };
            LinkClassifierApiRequestsOutstanding++;
            LinkClassifier.makeAjaxRequestWithDelay( q, 'callback redir' );
        }

        var defaultPrefix = 'redir-'; // make configurable?
        var prefix=( this.rawdata.redirects ? '' : defaultPrefix );
        var cats={};
        var missing={};
        var classes={};
        var uncategorized={};
        if(r.pages) for(var i in r.pages){
            classes[r.pages[i].title] = [];
            missing[r.pages[i].title]=( r.pages[i].missing !== undefined );
            if ( !missing[r.pages[i].title] ) {
                if( r.pages[i].categories !== undefined ){
                    cats[r.pages[i].title]=r.pages[i].categories.map(function(a){ return a.title; }).sort();
                    uncategorized[r.pages[i].title]=( r.pages[i].categories.length == 0 );
                }
                else {
                    uncategorized[r.pages[i].title]=true;
                }
            }
            if(r.pages[i].pageprops!==undefined){
                for ( var k in r.pages[i].pageprops ) {
                        if ( !LinkClassifier.props[k] ) {
                                continue;
                        }
                        var v = LinkClassifier.props[k];
                        if ( $.isFunction( v ) ) { // TODO: What does isFunction do that typeof v == 'function' doesn't?
                                v = v( r.pages[i].pageprops[k], k, r.pages[i].title );
                        }
                        classes[r.pages[i].title].push.apply( classes[r.pages[i].title], v );
                }
            }
            if(r.pages[i].protection!== undefined){
                classes[r.pages[i].title]=[];
                var x={};
                for(var j=r.pages[i].protection.length-1; j>=0; j--){
                    var p=prefix+'protection-'+r.pages[i].protection[j].type+'-'+r.pages[i].protection[j].level;
                    if( x[p] === undefined ){
                        x[p]=1;
                        classes[r.pages[i].title].push(p);
                    }
                    if(r.pages[i].protection[j].expiry=='infinity'){
                        p+='-indef';
                        if( x[p] === undefined ){
                            x[p]=1;
                            classes[r.pages[i].title].push(p);
                        }
                    }
                }
            }
            if(r.pages[i].flagged!==undefined){
                if(r.pages[i].lastrevid!=r.pages[i].flagged.stable_revid){
                    classes[r.pages[i].title].push('needs-review');
                }
            }
        }
        Array.prototype.forEach.call(a, function(a){
            if( a.wikipage === undefined ) return;
            var origHref = a.href;
            var curRedirMalformed = false;
            if( redir[a.wikipage] !== undefined ){
                $(a).addClass('redirect');
                a.wikipage=redir[a.wikipage];
                var newTitle='';
                if ( a.wikipage != '' ) {
                    newTitle = a.wikipage;
                    var cns=mw.config.get('wgCanonicalNamespace');
                    if(a.wikipage==(cns?cns+':':'')+mw.config.get('wgTitle'))
                        $(a).addClass('self-redirect');
                    else if(missing[a.wikipage])
                        $(a).addClass('broken-redirect');
                } 
                else {
                    newTitle = 'Malformed redirect (target begins with #)';
                    curRedirMalformed = true;
                    $(a).addClass('broken-redirect');
                }
                a.title=( window.LinkClassifierRedirTitleAppend === true ? a.title + '\n⤷' + newTitle : newTitle );
            }
            var m=origHref.match(/#.*/);
            if(m && m[0].substr(0,10)!=="#cite_note" && !curRedirMalformed){
                //HACK to avoid overwriting tooltips already modified by [[User:SoledadKabocha/markBlockedPlus.js]] (or the original markblocked)
                //TODO: logging, as this might not always work
                if ( !( typeof markBlocked == 'function' && !window.mbTipBox && $(a).hasClass('userlink') ) ) {
                    //Anchor unencoding. Since fromCharCode only supports the Basic Multilingual Plane, and fromCodePoint is still experimental,
                    //we handle only up to 3 UTF-8 bytes.

                    //Basic Latin (1 UTF-8 byte), avoiding control chars
                    var mfix = m[0].replace(/_/g,' ').replace(/\.([2-7][0-9A-F])/g, function(x,n){ return String.fromCharCode(parseInt(n,16)); });
                    mfix = mfix.replace(/\u007F/g, '.7F');

                    //2 UTF-8 bytes
                    var regex2b  = /\.([CD][0-9A-F])\.([89AB][0-9A-F])/;
                    var result2b = regex2b.exec( mfix );
                    while ( result2b !== null ) {
                        var byte1 = parseInt( result2b[ 1 ], 16 );
                        var byte2 = parseInt( result2b[ 2 ], 16 );
                        var fixedCode = ( ( byte1 & 0x1F ) << 6 ) | ( byte2 & 0x3F );
                        mfix = mfix.replace( regex2b, String.fromCharCode( fixedCode ) );
                        result2b = regex2b.exec( mfix );
                    }

                    //3 UTF-8 bytes
                    var regex3b  = /\.E([0-9A-F])\.([89AB][0-9A-F])\.([89AB][0-9A-F])/;
                    var result3b = regex3b.exec( mfix );
                    while ( result3b !== null ) {
                        var nibb1 = parseInt( result3b[ 1 ], 16 );
                        var byte2 = parseInt( result3b[ 2 ], 16 );
                        var byte3 = parseInt( result3b[ 3 ], 16 );
                        var fixedCode = ( nibb1 << 12 ) | ( ( byte2 & 0x3F ) << 6 ) | ( byte3 & 0x3F );
                        mfix = mfix.replace( regex3b, String.fromCharCode( fixedCode ) );
                        result3b = regex3b.exec( mfix );
                    }

                    a.title=a.title.replace(/#.*/,'')+mfix;
                }
            }
            if(LinkClassifier.intentionaldab.test(a.origwikipage)){
                $(a).addClass('intentional-disambiguation');
            }
            var shouldDetectUncat = window.LinkClassifierReallyEnableUncategorized && ( !window.LinkClassifierFilterUncategorized || !(/^((User|Talk|MediaWiki|Module|\w+ talk):|Main Page$)/.test( a.wikipage )) );
            if( shouldDetectUncat && uncategorized[a.wikipage] === true ) {
                $(a).addClass('uncategorized');
            }
            if( a.wikipage!=a.origwikipage && uncategorized[a.origwikipage] ) {
                $(a).addClass(defaultPrefix + 'uncategorized');
            }
            if( classes[a.wikipage] !== undefined ){
                for(var j=classes[a.wikipage].length-1; j>=0; j--)
                    $(a).addClass(classes[a.wikipage][j]);
            }
            if( a.wikipage!=a.origwikipage && classes[a.origwikipage] !== undefined ){
                for(var j=classes[a.origwikipage].length-1; j>=0; j--)
                    $(a).addClass(classes[a.origwikipage][j]);
            }
            var c1=[];
            if( cats[a.wikipage] !== undefined ){
                c1=c1.concat(cats[a.wikipage]);
            }
            if(a.wikipage!=a.origwikipage && cats[a.origwikipage] !== undefined ){
                c1=c1.concat(cats[a.origwikipage]);
            }
            if(c1.length>0){
                c1=c1.sort();
                for(var cls in LinkClassifier.cats){
                    var i1=c1.length-1;
                    var c2=LinkClassifier.cats[cls];
                    if(c2 instanceof RegExp){
                        while(i1>=0){
                            if(c2.test(c1[i1])){
                                $(a).addClass(cls);
                                break;
                            }
                            i1--;
                        }
                    } else {
                        var i2=c2.length-1;
                        while(i1>=0 && i2>=0){
                            if(c1[i1]==c2[i2]){
                                $(a).addClass(cls);
                                break;
                            }
                            (c1[i1]>c2[i2])?--i1:--i2;
                        }
                    }
                }
            }
        });
    },

    getPageName:function(url){
        var m=url.match(/\/wiki\/([^?#]+)/);
        if(!m) m=url.match(/\/w\/index.php\?(?:.*&)?title=([^&#]+)/);
        if(!m) return '';
        var t=decodeURIComponent(m[1]).replace(/_/g,' ');
        if(t.substr(0,6)=='Image:') t='File:'+t.substr(6);
        if(t.substr(0,11)=='Image talk:') t='File talk:'+t.substr(6);
        if(t.substr(0,8)=='Special:') t='';
        return t;
    },

    classifyChildren:function(node){
        mw.loader.using(['mediawiki.util','mediawiki.user'], function(){
            LinkClassifier.wasRun = true;

            var fixedNode = ( node === undefined ? document.getElementById( 'wikiPreview' ) : node );
            if ( !fixedNode ) fixedNode = document.getElementById( 'bodyContent' );
            if ( !fixedNode ) return;

            if ( typeof window.LinkClassifierStatusSel == 'string' ) {
                $( window.LinkClassifierStatusSel ).addClass( 'linkclassifier-started' );
            }

            var a=fixedNode.getElementsByTagName('A');
            if(!a || a.length==0) { LinkClassifier.forceCallChainedFunc( ); return; }

            var self= LinkClassifier.getPageName(location.href);
            var self2=wgPageName.replace(/_/g,' ');
            a=Array.prototype.map.call(a, function(a){
                a.wikipage='';
                if(/(^|\s)(external|extiw)(\s|$)/.test(a.className)) return '';
                if(!/(^|\s)(image)(\s|$)/.test(a.className)) a.className+=" nonimage";
                a.wikipage=LinkClassifier.getPageName(a.href);
                if(a.wikipage==self || a.wikipage==self2) a.wikipage='';
                a.origwikipage=a.wikipage;
                return a.wikipage;
            }).sort().filter(function(e,i,a){
                return e!=='' && (i==0 || a[i-1]!==e);
            });

            function processLinks(limit){
                var props = [];
                for ( var k in LinkClassifier.props ) {
                        props.push( k );
                }
                if ( a.length == 0 ) {
                    // This is where we detect linklessness for completely empty pages (0byte wikitext).
                    // This isn't a fatal error, so call the chained function in addition to removing the safety timeout
                    LinkClassifier.forceCallChainedFunc( );
                }
                while(a.length>0){
                    LinkClassifierApiRequestsOutstanding++;
                    var q={
                        format:'json',
                        action:'query',
                        titles:a.splice(0,limit).join('|'),
                        prop:'categories|pageprop|info|flagged',
                        redirects:1,
                        cllimit:'max',
                        inprop:'protection',
                        rawcontinue:1
                    };
                    if ( props.length <= limit ) {
                        q.ppprop = props.join( '|' );
                    }
                    LinkClassifier.makeAjaxRequestWithDelay( q, 'processLinks' );
                }
            }

            if( (window.LinkClassifierNoApihighlimits === true) || a.length<=100){
                // Not worth querying the API to see if the user has apihighlimits
                // (or user has manually declared that they lack the right)
                processLinks(50);
            } else {
                // Note mw.user.getRights queries the API
                mw.user.getRights(function(rights){
                    processLinks( (rights.indexOf('apihighlimits')>=0) ? 500 : 50 );
                });
            }
        });
    },
 
    onLoad:function(){
        if(window.LinkClassifierOnDemand === true) return;
        if(window.AJAXPreview) window.AJAXPreview.AddOnLoadHook(LinkClassifier.classifyChildren);
        LinkClassifier.onDemand();
    },
 
    onDemand:function(){
        mw.hook( 'LinkClassifier' ).fire( this );

        // We need to reset this here in case we have already been run on the current page
        LinkClassifier.isFirstRequest = true;

        LinkClassifierChainedFuncCalledAlready = false; // belongs at the top of classifyChildren or perhaps processLinks?
        LinkClassifierApiRequestsOutstanding = 0;

        var node=document.getElementById('wikiPreview');
        if(!node) node=document.getElementById('bodyContent');
        if(node) {
            LinkClassifier.initUnloadHandlerIfNeeded();
            LinkClassifier.classifyChildren(node);
        }
        if ( window.LinkClassifierOnDemand === true ) {
            var myPortletLink = $('#ca-linkclassifier');
            if ( myPortletLink ) myPortletLink.remove( );
        }
    },

    rerun:function() {
        if ( LinkClassifier.wasRun ) {
            LinkClassifier.onDemand();
        }
    }
};

$(document).ready(function(){
    if( window.LinkClassifierOnDemand === true ) {
        mw.util.addPortletLink(defaultPref( window.LinkClassifierOnDemandLoc, 'p-cactions' ), 'javascript:LinkClassifier.onDemand()', defaultPref( window.LinkClassifierOnDemandText, 'linkclassifier' ), 'ca-linkclassifier');
    }
    else {
        LinkClassifier.onLoad( );
    }

    if ( Math.random( ) < 1/64 ) {
        mw.util.jsMessage( '(!) This fork of linkclassifier is unmaintained and lacks Anomie\'s draft-detection feature. '
                         + 'Please wait for SoledadKabocha to rework it, at which time this message will be removed, '
                         + 'or switch back to Anomie\'s official version. '
                         );
    }
});