MediaWiki:Gadget-NSFW.js

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/**
 * Hides NSFW images under a spoiler.
 * This gadget can be used both for Wikimedia Commons and for other wiki projects.
 *
 * @author putnik, 2019-2020
 */
( function ( mw, $ ) {
	var nsfwTopics = [
		'Q291', // pornography
		'Q496', // feces
		'Q608', // human sexual activity
		'Q5880', // vagina
		'Q5887', // orgasm
		'Q9103', // breast
		'Q10791', // nudity
		'Q10816', // sex toy
		'Q40446', // nude
		'Q42165', // buttocks
		'Q124490', // violence
		'Q133993', // erection
		'Q174471', // scrotum
		'Q181001', // erotica
		'Q188641', // nipple
		'Q650891', // glans penis
		'Q673203', // foreskin
		'Q843533', // areola
		'Q844482', // killing
		'Q1058795', // body fluid
		'Q1406501', // labia
		'Q2148678', // sexual penetration
		'Q2192288', // vulva
		'Q3258546', // human anus
		'Q4620674', // sex organ
		'Q11722446', // mons pubis
	];
	var imagesData = [];
	var $images;
	var isCommons = 'commonswiki' === mw.config.get( 'wgDBname' );
	var commonsApi;
	var foundTopics = [];

	var init = function() {
		commonsApi = isCommons ? new mw.Api() : new mw.ForeignApi( '//commons.wikimedia.org/w/api.php' );
		findImages();
	};

	var findImages = function() {
		$images = $( '.image:not(.nsfw)' ).filter( function() {
			var $img = $( this ).find( 'img' );
			return $img.width() * $img.height() >= 4000 && $img.attr( 'src' ).match( 'upload.wikimedia.org' );
		} );

		var imageTitles = $images.map( function() {
			var src = $( this ).find( 'img' ).attr( 'src' );
			return 'File:' + parseImageName( src );
		} ).toArray();
		
		for ( var offset = 0; offset < imageTitles.length; offset += 50 ) {
			loadDepicts( imageTitles.slice( offset, offset + 50 ) );
		}
	};

	var loadDepicts = function( imageTitles ) {
		commonsApi.get( {
			action: 'wbgetentities',
			props: [ 'info', 'claims' ],
			sites: 'commonswiki',
			titles: imageTitles,
		} ).done( function ( data ) {
			if ( data.entities === undefined ) {
				return;
			}
			for ( var pageMid in data.entities ) {
				var entity = data.entities[ pageMid ];
				if ( entity.statements === undefined || entity.statements.P180 === undefined ) {
					continue;
				}
				imagesData.push( {
					mid: pageMid,
					title: entity.title,
					depicts: entity.statements.P180.map( function( value ) {
						return value.mainsnak.datavalue.value.id;
					} ),
				} );
			}
			findNsfwTopics();
		} );
	};

	var findNsfwTopics = function() {
		var topics = imagesData.map( function( imageData ) {
			return imageData.depicts;
		} ).flat();
		topics = topics.filter( function( value, index, self ) { 
			return self.indexOf( value ) === index;
		} );

		var sparqlQuery = `SELECT ?depicts WHERE { 
		  ?depicts wdt:P31*/wdt:P279* ?nsfw .
		  VALUES ?depicts {wd:` + topics.join( ' wd:' ) + `}
		  VALUES ?nsfw {wd:` + nsfwTopics.join( ' wd:' ) + `}
		}`;

		$.ajax( {
			url: 'https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent( sparqlQuery ),
			success: function ( data ) {
				if ( !data.results.bindings.length ) {
					return;
				}
				for ( var i in data.results.bindings ) {
					var row = data.results.bindings[ i ];
					var topic = row.depicts.value.replace( 'http://www.wikidata.org/entity/', '' );
					foundTopics.push( topic );
				}
				if ( foundTopics.length ) {
					hideNsfwImages();
				}
			}
		} );
	};

	var hideNsfwImages = function() {
		mw.util.addCSS( `
			.image.nsfw {
				display: inline-block;
				vertical-align: top;
				position: relative;
				overflow: hidden;
			}

			.image.nsfw>img {
				filter: blur(20px);
			}

			.image.nsfw::before {
				content: "NSFW";
				position: absolute;
				left: 50%;
				top: 50%;
				z-index: 1;
				background: #fff;
				border-radius: 2em;
				height: 3em;
				line-height: 3em;
				width: 6em;
				margin-left: -3em;
				margin-top: -1.5em;
				opacity: .6;
				color: #222;
				text-align: center;
			}

			.image.nsfw:hover::before {
				opacity: .8;
			}
		` );

		var imageNames = [];
		var intersectsFoundTopics = function( value ) {
			return -1 !== foundTopics.indexOf( value );
		};
		for ( var i in imagesData ) {
			if ( imagesData[ i ].depicts.filter( intersectsFoundTopics ).length > 0 ) {
				imageNames.push( imagesData[ i ].title.replace( /^File:/, '' ) );
			}
		}

		$images = $( '.image' ).each( function() {
			var $image = $( this );
			var src = $image.find( 'img' ).attr( 'src' );
			var name = parseImageName( src );
			if ( -1 !== imageNames.indexOf( name ) ) {
				$image.addClass( 'nsfw' );
			}
		} );

		$( '.image.nsfw' )
			.off( 'click' )
			.one( 'click', onClick );
	};
	
	var parseImageName = function( src ) {
		var name = src.replace( /\/(?:lossless\-page\d+\-|lang[a-z]+\-)?\d+px\-[^\/]+$/, '' );
		name = name.replace( /^.*\/([^\/]+)$/, '$1' );
		return decodeURIComponent( name.replace( /_/g, ' ' ) );
	};

	var onClick = function( e ) {
		e.preventDefault();
		$( this ).removeClass( 'nsfw' );
	};

	$.when(
		$.ready,
		mw.loader.using( [
			( isCommons ? 'mediawiki.api' : 'mediawiki.ForeignApi' ),
			'mediawiki.util',
		] )
	).done( init );
}( mediaWiki, jQuery ) );