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.6.0
// @match *://boards.4chan.org/*
// @match *://boards.4channel.org/*
// @run-at document-start
// ==/UserScript==
(function() {
var doInit;
var doProcessFile;
var doProcessFiles;
var doPlayFile;
var doMakeKey;
var players;
var allow;
allow = [
"4cdn.org",
"catbox.moe",
"lewd.se"
];
document.addEventListener(
"4chanXInitFinished",
function (event) {
doInit();
}
);
doInit = function () {
var observer;
if (!document.documentElement.classList.contains("fourchan-x")) {
return;
}
players = {};
doProcessFiles(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) {
doProcessFiles(node);
doPlayFile(node);
}
}
);
}
}
);
}
);
observer
.observe(
document.body,
{
childList: true,
subtree: true
}
);
};
doProcessFile = function (file) {
var fileLink;
var fileName;
var key;
var match;
var player;
var playerSrc;
var playerSrcHost;
var isHostAllowed;
if (!file.classList.contains("file")) {
return;
}
fileLink = file.querySelector(".file-info > a");
if (!fileLink) {
return;
}
fileName = null;
[
file.querySelector(".file-info .fnfull"),
file.querySelector(".file-info > a")
].forEach(
function (node) {
if (fileName) {
return;
}
if (node && node.textContent) {
fileName = node.textContent;
}
}
);
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;
}
playerSrc = match[1];
if (playerSrc.includes("%")) {
try {
playerSrc = decodeURIComponent(playerSrc);
} catch (error) {
return;
}
}
if (playerSrc.match(/^(https?\:)?\/\//) === null) {
playerSrc = (location.protocol + "//" + playerSrc);
}
try {
playerSrc = new URL(playerSrc);
} catch (error) {
return;
}
playerSrcHost = playerSrc.hostname;
playerSrcHost = playerSrcHost.toLowerCase();
isHostAllowed = false;
allow.forEach(
function (item) {
isHostAllowed = (isHostAllowed || (playerSrcHost === item));
isHostAllowed = (isHostAllowed || playerSrcHost.endsWith("." + item));
}
);
if (!isHostAllowed) {
return;
}
player = new Audio();
player.preload = "none";
player.volume = 0.80;
player.loop = true;
player.src = playerSrc.href;
players[key] = player;
};
doProcessFiles = 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) {
doProcessFile(file);
}
);
}
);
};
doPlayFile = function (target) {
var key;
var player;
var interval;
if (!(
target.id === "ihover" ||
target.className === "full-image"
)) {
return;
}
key = doMakeKey(target.src);
if (!key) {
return;
}
player = players[key];
if (!player) {
return;
}
if (!player.paused) {
if (player.dataset.play == 1) {
return;
} else {
player.pause();
}
}
player.dataset.play = 1;
player.dataset.moveTime = 0;
player.dataset.moveLast = 0;
switch (target.tagName) {
case "IMG":
player.loop = true;
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.1
) {
player.currentTime = target.currentTime;
}
if (player.paused) {
player.play();
}
}
}
}
} else {
clearInterval(interval);
player.dataset.play = 0;
player.pause();
}
},
(1000/30)
);
};
doMakeKey = function (link) {
var match;
match = link.match(/\.(?:4cdn|4chan)\.org\/(.+?)\/(\d+?)\.(.+?)$/);
if (match) {
return (match[1] + "." + match[2]);
}
return null;
};
})();