User:DannyS712/FindIPActivity.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
// <nowiki>
// Quick script to find where an IP has been active recently
// @author DannyS712
$(() => {
const FindIPActivity = {};
window.FindIPActivity = FindIPActivity;
FindIPActivity.init = function () {
window.document.title = 'FindIPActivity';
$( '#firstHeading' ).text( 'FindIPActivity' );
mw.loader.using(
[ 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'mediawiki.api', 'mediawiki.ForeignApi', 'mediawiki.util' ],
FindIPActivity.run
);
};
FindIPActivity.run = function () {
var preloadIP = mw.util.getParamValue( 'targetIP' );
var targetIP = new OO.ui.TextInputWidget( {
value: preloadIP
} );
var targetIPLayout = new OO.ui.FieldLayout(
targetIP,
{ label: 'Target IP' }
);
var submit = new OO.ui.ButtonInputWidget( {
label: 'Search',
flags: [
'primary',
'progressive'
]
} );
var clickHandler = function () {
console.log( targetIP );
FindIPActivity.onSubmit( targetIP.value );
};
submit.on( 'click', clickHandler );
$( window ).on( 'keypress', function ( e ) {
// press enter to start
if ( e.which == 13 ) {
submit.simulateLabelClick();
clickHandler();
}
} );
var fieldSet = new OO.ui.FieldsetLayout( {
label: 'Show a list of wikis where an IP has edited in the last 24 hours (the edits might be deleted though)'
} );
fieldSet.addItems( [
targetIPLayout,
new OO.ui.FieldLayout( submit )
] );
var $results = $( '<div>' )
.attr( 'id', 'FindIPActivity-results' );
$( '#mw-content-text' ).empty().append(
$( '<span>' ).append(
'Source code: ',
$( '<a>' )
.attr( 'href', 'https://meta.wikimedia.org/wiki/User:DannyS712/FindIPActivity.js' )
.attr( 'target', '_blank' )
.text( '[[:m:User:DannyS712/FindIPActivity.js]]' )
),
fieldSet.$element,
$( '<hr>' ),
$results
);
// Trigger automatically from preload
if ( preloadIP ) {
submit.simulateLabelClick();
clickHandler();
}
};
FindIPActivity.onSubmit = function ( targetIP ) {
var $res = $( '#FindIPActivity-results' );
$res.empty();
$res.append( $( '<p>' ).text( 'Getting a list of wikis to check...' ) );
console.log( targetIP );
FindIPActivity.getSitesList().then(
function ( allSiteURLs ) {
console.log( 'Checking against:' );
console.log( allSiteURLs );
$res.append( $( '<p>' ).text( 'Checking the APIs...' ) );
FindIPActivity.getSitesWithEdits( allSiteURLs, targetIP ).then(
function ( allSitesWithEdits ) {
console.log( 'Found recent edits on:' );
console.log( allSitesWithEdits );
FindIPActivity.showResults( allSitesWithEdits, targetIP );
}
);
},
FindIPActivity.onErrHandler
);
};
FindIPActivity.onErrHandler = function () {
// Shared error handler
alert( 'Something went wrong' );
console.log( arguments );
};
// So we only need to query the SiteMatrix API once
FindIPActivity.siteListCache = false;
FindIPActivity.getSitesList = function () {
return new Promise( function ( resolve ) {
if ( FindIPActivity.siteListCache !== false ) {
resolve( FindIPActivity.siteListCache );
return;
}
new mw.Api().get( {
action: 'sitematrix',
smsiteprop: 'url',
smlimit: 'max',
formatversion: 2
} ).then(
function ( response ) {
console.log( response );
// Future proofing - at some point there max be more than 500 language
// codes, and I'll need to add continuation logic. Until then
if ( response[ 'query-continue' ] ) {
alert( 'The script currently does not have continuation logic, only some sites may be shown. Contact DannyS712' );
}
var allSiteURLs = [];
// response.sitematrix has integer keys for the language entries,
// a `count`, and an array of special wikis. We handle specials
// later
Object.keys( response.sitematrix ).forEach( function ( key ) {
if ( key === 'count' || key === 'specials' ) {
return;
}
response.sitematrix[ key ].site.forEach( function ( langSite ) {
// 'closed' is reserved, can't use dot notation
if ( !( langSite['closed'] ) ) {
allSiteURLs.push( langSite.url );
}
} );
} );
response.sitematrix.specials.forEach( function( specialWiki ) {
// 'closed' is reserved, can't use dot notation
if ( !(
specialWiki.private ||
specialWiki['closed'] ||
specialWiki.fishbowl ||
specialWiki.nonglobal
) ) {
allSiteURLs.push( specialWiki.url );
}
} );
allSiteURLs = allSiteURLs.map(
function ( url ) {
return url.replace( /^https?:/, '' );
}
);
console.log( allSiteURLs );
FindIPActivity.siteListCache = allSiteURLs;
resolve( allSiteURLs );
},
FindIPActivity.onErrHandler
);
} );
};
// So that it can be set in the checkSite method
FindIPActivity.sitesWithEdits = [];
FindIPActivity.getSitesWithEdits = function ( allSiteURLs, targetIP ) {
// clear out sitesWithEdits, then map each url to a promise that resolves
// once the site is checked; within that check, sitesWithEdits is updated
// if the specific site has edits
FindIPActivity.sitesWithEdits = [];
return new Promise( function ( resolve ) {
Promise.all(
allSiteURLs.map(
function ( siteURL ) {
return FindIPActivity.checkSite( siteURL, targetIP );
}
)
).then(
function () {
console.log( 'has edits on:' );
console.log( FindIPActivity.sitesWithEdits );
resolve( FindIPActivity.sitesWithEdits );
}
);
} );
};
FindIPActivity.checkSite = function ( url, targetIP ) {
return new Promise( function ( resolve ) {
// Checking if there is an edit made by the IP within the last 24 hours
// Get the time from 24 hours ago, in UNIX timestamp format (seconds since January 1, 1970)
// Date.now returns milliseconds
var startTS = Math.floor( ( Date.now() / 1000 ) - ( 60 * 60 * 24 ) );
new mw.ForeignApi( url + '/w/api.php' ).get( {
action: 'query',
list: 'allrevisions|alldeletedrevisions',
// allrevisions props
arvlimit: 1, // just checking if there is one
arvuser: targetIP,
arvdir: 'newer',
arvstart: startTS, // newer than 24 hours ago
// alldeletedrevisions props
adrlimit: 1,
adruser: targetIP,
adrdir: 'newer',
adrstart: startTS,
adrprop: 'ids',
formatversion: 2
} ).then(
function ( response ) {
console.log( response );
// could have existing or deleted
var haveEdits = (
response.query.allrevisions.length !== 0 ||
response.query.alldeletedrevisions.length !== 0
);
if ( haveEdits ) {
FindIPActivity.sitesWithEdits.push( url );
}
resolve( haveEdits );
},
function () {
FindIPActivity.onErrHandler( arguments );
// assume false
resolve( false );
}
);
} );
};
FindIPActivity.showResults = function ( allSitesWithEdits, targetIP ) {
var $res = $( '#FindIPActivity-results' );
if ( allSitesWithEdits.length === 0 ) {
$res.append(
$( '<p>' ).text( 'No edits found in the last 24 hours' )
);
return;
}
$res.append(
$( '<p>' ).text( 'Recent edits found:' )
);
var $contribsLinks = $( '<ul>' );
allSitesWithEdits.forEach(
function ( siteWithEdits ) {
$contribsLinks.append(
$( '<li>' ).append(
$( '<a>' )
.attr( 'href', siteWithEdits + '/wiki/Special:Contributions/' + targetIP )
.attr( 'target', '_blank' )
.text( siteWithEdits.replace( /^\/\//, '' ) + '/wiki/Special:Contributions/' + targetIP )
)
);
}
);
$res.append( $contribsLinks );
};
});
$( document ).ready( () => {
mw.loader.using(
[ 'mediawiki.util' ],
function () {
// Always add a sidebar link
mw.util.addPortletLink(
'p-tb',
'/wiki/Special:BlankPage/FindIPActivity',
'FindIPActivity'
);
// On IP contributions, add to p-views with preloaded
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) == 'Contributions' ) {
var user = mw.config.get( 'wgRelevantUserName' );
if ( user && mw.util.isIPAddress( user ) ) {
mw.util.addPortletLink(
'p-views',
'/wiki/Special:BlankPage/FindIPActivity?targetIP=' + user,
'FindIPActivity'
);
}
}
}
);
if ( mw.config.get( 'wgNamespaceNumber' ) === -1 ) {
const page = mw.config.get( 'wgCanonicalSpecialPageName' );
if ( page === 'Blankpage' ) {
const page2 = mw.config.get( 'wgTitle' ).split( '/' );
if ( page2[1] && page2[1] === 'FindIPActivity' ) {
window.FindIPActivity.init();
}
}
}
});
// </nowiki>