4chan External Sounds

Plays audio associated with images on 4chan.

Per 03-08-2019. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name 4chan External Sounds
// @namespace b4k
// @description Plays audio associated with images on 4chan.
// @author Bakugo
// @version 1.0.0
// @match *://boards.4chan.org/*
// @run-at document-start
// ==/UserScript==

(function() {
	var regexFileURL;
	var regexTrigger;
	var audioPlayers;
	var processFile;
	var processFiles;
	var getFileKey;
	var playFile;
	
	regexFileURL = /\/\/i.4cdn.org\/(.+?)\/(\d+?)\.(.+?)$/;
	regexTrigger = /\[audio[\|\$](.*?)\]/i;
	
	audioPlayers = {};
	
	document.addEventListener("4chanXInitFinished", function (event) {
		if (!document.documentElement.classList.contains("fourchan-x")) {
			return;
		}
		
		processFiles(document.body);
		
		(new MutationObserver(function (mutations) {
			mutations.forEach(function (mutation) {
				if (mutation.type === "childList") {
					mutation.addedNodes.forEach(function (node) {
						if (node.nodeType === Node.ELEMENT_NODE) {
							processFiles(node);
							playFile(node);
						}
					});
				}
			});
		})).observe(document.body, {
			childList: true,
			subtree: true
		});
	});
	
	processFile = function (file) {
		var fileLink;
		var fileKey;
		var fileName;
		var fileNameEls;
		var fileNameMatch;
		var audioURL;
		var audioPlayer;
		
		if (!file.classList.contains("file")) {
			return;
		}
		
		fileLink =
			file.querySelector(".file-info > a");
		
		if (!fileLink) {
			return;
		}
		
		fileKey =
			getFileKey(fileLink.href);
		
		if (!fileKey) {
			return;
		}
		
		fileName = null;
		
		fileNameEls = [
			file.querySelector(".file-info .fnfull"),
			file.querySelector(".file-info > a")
		];
		
		fileNameEls.forEach(function (node) {
			if (fileName) {
				return;
			}
			
			if (node && node.textContent) {
				fileName = node.textContent;
			}
		});
		
		if (!fileName) {
			return;
		}
		
		fileNameMatch =
			fileName.match(regexTrigger);
		
		if (!fileNameMatch) {
			return;
		}
		
		audioURL = fileNameMatch[1];
		audioURL = window.decodeURIComponent(audioURL);
		audioURL = (audioURL.match(/^(https?\:)?\/\//) ? audioURL : ("http://" + audioURL));
		
		audioPlayer = document.createElement("audio");
		audioPlayer.src = audioURL;
		audioPlayer.preload = "none";
		
		audioPlayers[fileKey] = audioPlayer;
	};
	
	processFiles = function (target) {
		target.querySelectorAll(".post")
			.forEach(function (post) {
				if (post.parentElement.parentElement.id === "qp") {
					return;
				}
				
				if (post.parentElement.classList.contains("noFile")) {
					return;
				}
				
				post.querySelectorAll(".file")
					.forEach(function (file) {
						processFile(file);
					});
			});
	};
	
	getFileKey = function (fileURL) {
		var fileURLMatch;
		
		fileURLMatch =
			fileURL.match(regexFileURL);
		
		if (!fileURLMatch) {
			return null;
		}
		
		return (fileURLMatch[1] + "." + fileURLMatch[2]);
	};
	
	playFile = function (target) {
		var fileKey;
		var audioPlayer;
		var checkInterval;
		
		if (!(target.id === "ihover" || target.className === "full-image")) {
			return;
		}
		
		fileKey =
			getFileKey(target.src);
		
		if (!fileKey) {
			return;
		}
		
		audioPlayer =
			audioPlayers[fileKey];
		
		if (!audioPlayer) {
			return;
		}
		
		if (!audioPlayer.paused) {
			return;
		}
		
		switch (target.tagName) {
			case "IMG":
				audioPlayer.currentTime = 0;
				audioPlayer.loop = true;
				audioPlayer.play();
				
				break;
			
			case "VIDEO":
				audioPlayer.currentTime = target.currentTime;
				audioPlayer.loop = false;
				audioPlayer.play();
				
				break;
			
			default:
				return;
		}
		
		checkInterval =
			window.setInterval(function () {
				if (document.body.contains(target)) {
					if (target.tagName === "VIDEO") {
						if (audioPlayer.duration) {
							if (target.currentTime > audioPlayer.duration) {
								audioPlayer.pause();
							} else {
								if (Math.abs(target.currentTime - audioPlayer.currentTime) > 0.1) {
									audioPlayer.currentTime = target.currentTime;
								}
								
								if (!target.paused && audioPlayer.paused) {
									audioPlayer.play();
								}
								
								if (target.paused && !audioPlayer.paused) {
									audioPlayer.pause();
								}
							}
						}
					}
				} else {
					window.clearInterval(checkInterval);
					audioPlayer.pause();
				}
			}, 10);
	};
})();