Plays audio associated with images on 4chan.
// ==UserScript==
// @name 4chan External Sounds
// @namespace b4k
// @description Plays audio associated with images on 4chan.
// @author Bakugo
// @version 1.7.0
// @match *://boards.4chan.org/*
// @match *://boards.4channel.org/*
// @run-at document-start
// ==/UserScript==
(function() {
var doInit;
var doParseFile;
var doParseFiles;
var doPlayFile;
var doMakeKey;
var allow;
var players;
var isChanX;
allow = [
"4cdn.org",
"catbox.moe",
"dmca.gripe",
"lewd.se",
"pomf.cat",
"zz.ht"
];
document.addEventListener(
"DOMContentLoaded",
function (event) {
setTimeout(
function () {
if (
document.body.classList.contains("ws") ||
document.body.classList.contains("nws")
) {
isChanX = false;
doInit();
}
},
(1)
);
}
);
document.addEventListener(
"4chanXInitFinished",
function (event) {
if (
document.documentElement.classList.contains("fourchan-x") &&
document.documentElement.classList.contains("sw-yotsuba")
) {
isChanX = true;
doInit();
}
}
);
doInit = function () {
var observer;
if (players) {
return;
}
players = {};
doParseFiles(document.body);
observer =
new MutationObserver(
function (mutations) {
mutations.forEach(
function (mutation) {
if (mutation.type === "childList") {
mutation.addedNodes.forEach(
function (node) {
if (node.nodeType === Node.ELEMENT_NODE) {
doParseFiles(node);
doPlayFile(node);
}
}
);
}
}
);
}
);
observer
.observe(
document.body,
{
childList: true,
subtree: true
}
);
};
doParseFile = function (file) {
var fileLink;
var fileName;
var key;
var match;
var player;
var link;
if (!file.classList.contains("file")) {
return;
}
if (isChanX) {
fileLink = file.querySelector(".fileText .file-info > a");
} else {
fileLink = file.querySelector(".fileText > a");
}
if (!fileLink) {
return;
}
if (!fileLink.href) {
return;
}
fileName = null;
if (isChanX) {
[
file.querySelector(".fileText .file-info .fnfull"),
file.querySelector(".fileText .file-info > a")
].some(
function (node) {
if (node) {
if (node.textContent) {
fileName = node.textContent;
return true;
}
}
return false;
}
);
} else {
[
file.querySelector(".fileText"),
file.querySelector(".fileText > a")
].some(
function (node) {
if (node) {
if (node.title) {
fileName = node.title;
return true;
}
if (
node.tagName === "A" &&
node.textContent
) {
fileName = node.textContent;
return true;
}
}
return false;
}
);
}
if (!fileName) {
return;
}
fileName = fileName.replace(/\-/, "/");
key = doMakeKey(fileLink.href);
if (!key) {
return;
}
if (players[key]) {
return;
}
match = fileName.match(/[\[\(\{](?:audio|sound)[ \=\:\|\$](.*?)[\]\)\}]/i);
if (!match) {
return;
}
link = match[1];
if (link.includes("%")) {
try {
link = decodeURIComponent(link);
} catch (error) {
return;
}
}
if (link.match(/^(https?\:)?\/\//) === null) {
link = (location.protocol + "//" + link);
}
try {
link = new URL(link);
} catch (error) {
return;
}
if (
allow.some(
function (item) {
return (
link.hostname.toLowerCase() === item ||
link.hostname.toLowerCase().endsWith("." + item)
);
}
) == false
) {
return;
}
player = new Audio();
player.preload = "none";
player.volume = 0.80;
player.loop = true;
player.src = link.href;
players[key] = player;
};
doParseFiles = 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) {
doParseFile(file);
}
);
}
);
};
doPlayFile = function (target) {
var key;
var player;
var interval;
if (isChanX) {
if (!(
target.id === "ihover" ||
target.className === "full-image"
)) {
return;
}
} else {
if (!(
target.id === "image-hover" ||
target.className === "expanded-thumb" ||
target.className === "expandedWebm"
)) {
return;
}
}
if (!target.src) {
return;
}
key = doMakeKey(target.src);
if (!key) {
return;
}
player = players[key];
if (!player) {
return;
}
if (!player.paused) {
if (player.dataset.play == 1) {
if (isChanX) {
return;
} else {
player.dataset.again = 1;
}
} else {
player.pause();
}
}
if (player.dataset.play != 1){
player.dataset.play = 1;
player.dataset.again = 0;
player.dataset.moveTime = 0;
player.dataset.moveLast = 0;
}
switch (target.tagName) {
case "IMG":
player.loop = true;
if (player.dataset.again != 1) {
player.currentTime = 0;
player.play();
}
break;
case "VIDEO":
player.loop = false;
player.currentTime = target.currentTime;
player.play();
break;
default:
return;
}
if (player.paused) {
document.dispatchEvent(
new CustomEvent(
"CreateNotification",
{
bubbles: true,
detail: {
type: "warning",
content: "Your browser blocked autoplay, click anywhere on the page to activate it and try again.",
lifetime: 5
}
}
)
);
}
interval =
setInterval(
function () {
if (document.body.contains(target)) {
if (target.tagName === "VIDEO") {
if (target.currentTime != (+player.dataset.moveLast)) {
player.dataset.moveTime = Date.now();
player.dataset.moveLast = target.currentTime;
}
if (player.duration != NaN) {
if (
target.paused == true ||
target.duration == NaN ||
target.currentTime > player.duration ||
((Date.now() - (+player.dataset.moveTime)) > 300)
) {
if (!player.paused) {
player.pause();
}
} else {
if (
player.paused ||
Math.abs(target.currentTime - player.currentTime) > 0.100
) {
player.currentTime = target.currentTime;
}
if (player.paused) {
player.play();
}
}
}
}
} else {
clearInterval(interval);
if (player.dataset.again == 1) {
player.dataset.again = 0;
} else {
player.pause();
player.dataset.play = 0;
}
}
},
(1000/30)
);
};
doMakeKey = function (link) {
var match;
match = link.match(/\.(?:4cdn|4chan)\.org\/(.+?)\/(\d+?)\.(.+?)$/);
if (match) {
return (match[1] + "." + match[2]);
}
return null;
};
})();