Twitch FrankerFaceZ Emotes in VODs

Enables FrankerFaceZ emotes in VODs, requires FrankerFaceZ (obviously)

// ==UserScript==
// @name Twitch FrankerFaceZ Emotes in VODs
// @namespace b4k
// @description Enables FrankerFaceZ emotes in VODs, requires FrankerFaceZ (obviously)
// @author Bakugo
// @version 1.0.0
// @match *://www.twitch.tv/*
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==

(function () {
	var doMain;
	var doShouldRun;
	var doGetVodUser;
	var doProcessNode;
	var doProcessNodeCh;
	var doLoopUpdateRoom;
	var doLoopUpdateChat;
	var observer;
	var obsTarget;
	var emoteCache;
	
	doMain =
		function () {
			observer =
				new MutationObserver(
					function (mutations) {
						mutations.forEach(
							function (mutation) {
								if (mutation.type === "childList") {
									mutation.addedNodes.forEach(
										function (node) {
											doProcessNode(node);
										}
									);
								}
							}
						);
					}
				);
			
			obsTarget = null;
			
			emoteCache = new Map();
			
			window.setInterval(function () { doLoopUpdateRoom(); }, (1000*1));
			window.setInterval(function () { doLoopUpdateChat(); }, (1000*3));
		};
	
	doShouldRun =
		function () {
			return (
				unsafeWindow.ffz !== undefined &&
				window.location.pathname.match(/^\/videos\/(\d+?)/) !== null &&
				document.querySelector("[data-test-selector=\"video-player\"]") !== null
			);
		};
	
	doGetVodUser =
		function () {
			var tmp;
			
			tmp = document.querySelector(
				".video-player .player-streaminfo__name");
			
			if (tmp === null) {
				return null;
			}
			
			return tmp
				.textContent
				.toLowerCase();
		};
	
	doProcessNode =
		function (node) {
			var login;
			var ffzRoom;
			var emoteList;
			var eMessageCnt;
			
			if (!doShouldRun()) {
				return;
			}
			
			if (node.tagName !== "LI") {
				return;
			}
			
			login = doGetVodUser();
			
			if (login === null) {
				return;
			}
			
			ffzRoom = unsafeWindow.ffz.__modules.chat.rooms[login];
			
			if (
				ffzRoom === undefined ||
				ffzRoom._id === null ||
				emoteCache.has(login) === false
			) {
				(function () {
					var tmp;
					
					tmp = node;
					
					window.setTimeout(
						function () {
							doProcessNode(tmp);
						},
						
						(1000/3)
					);
				})();
				
				return;
			}
			
			eMessageCnt = node.querySelector(
				"[data-test-selector=\"comment-message-selector\"]");
			
			if (!eMessageCnt) {
				return;
			}
			
			emoteList = emoteCache.get(login);
			
			node.setAttribute("hidden", "");
			
			window.setTimeout(
				function () {
					node.removeAttribute("hidden");
					
					eMessageCnt.children[1]
						.childNodes
							.forEach(
								function (node) {
									doProcessNodeCh(
										node,
										emoteList
									);
								}
							);
				},
				
				(0)
			);
		};
	
	doProcessNodeCh =
		function (node, emoteList) {
			var nodeTmp;
			var nodeVal;
			
			if (!(
				node.nodeType === Node.TEXT_NODE ||
				node.nodeType === Node.ELEMENT_NODE
			)) {
				return;
			}
			
			if (node.nodeType === Node.ELEMENT_NODE) {
				if (
					node.tagName !== "SPAN" ||
					node.getAttribute("class") !== null
				) {
					return;
				}
				
				if (
					node.children.length > 0 ||
					node.childNodes.length > 1
				) {
					node.childNodes
						.forEach(
							function (node) {
								doProcessNodeCh(node, emoteList);
							}
						)
					
					return;
				}
			}
			
			if (node.nodeType === Node.TEXT_NODE) {
				nodeTmp = document.createElement("span");
				nodeTmp.textContent = node.textContent;
				node.after(nodeTmp);
				node.textContent = "";
				node = nodeTmp;
			}
			
			nodeVal = node.innerHTML;
			nodeVal = nodeVal.split(" ");
			
			emoteList
				.forEach(
					function (emote) {
						nodeVal.forEach(
							function (value, idx) {
								if (value === emote.name) {
									nodeVal[idx] = (
										"<img" +
										" alt=\"" + emote.name + "\"" +
										" title=\"FFZ: " + emote.name + "\"" +
										" src=\"" + emote.urls["1"] + "\"" +
										" srcset=\"" + emote.srcSet + "\"" +
										" class=\"chat-image chat-line__message--emote tw-inline-block\"" +
										" style=\"width: " + emote.width + "px; height: " + emote.height + "px;\"" +
										">"
									);
								}
							}
						);
					}
				);
			
			nodeVal = nodeVal.join(" ");
			
			if (node.innerHTML !== nodeVal) {
				node.innerHTML = nodeVal;
			}
		};
	
	doLoopUpdateRoom =
		function () {
			var login;
			var ffzRoom;
			var emoteSets;
			var emoteList;
			
			if (!doShouldRun()) {
				return;
			}
			
			login = doGetVodUser();
			
			if (login === null) {
				return;
			}
			
			ffzRoom = unsafeWindow.ffz.__modules.chat.getRoom(null, login);
			
			if (
				ffzRoom._id !== null &&
				emoteCache.has(login) === false &&
				unsafeWindow.ffz.__modules.chat.emotes.default_sets._cache.length > 0
			) {
				emoteSets = [];
				emoteSets = emoteSets.concat(unsafeWindow.ffz.__modules.chat.emotes.default_sets._cache);
				emoteSets = emoteSets.concat(unsafeWindow.ffz.__modules.chat.rooms[login].emote_sets._cache);
				
				emoteList = new Set();
				
				emoteSets.forEach(
					function (setId) {
						Object.values(unsafeWindow.ffz.__modules.chat.emotes.emote_sets[setId].emotes)
							.forEach(
								function (emote) {
									emoteList.add(emote);
								}
							);
					}
				);
				
				emoteCache.set(login, emoteList);
			}
		};
	
	doLoopUpdateChat =
		function () {
			var tmp;
			
			if (!doShouldRun()) {
				obsTarget = null;
				observer.disconnect();
				
				return;
			}
			
			tmp = document.querySelector(
				"[data-test-selector=\"video-chat-wrapper\"] ul");
			
			if (obsTarget === tmp) {
				return;
			}
			
			obsTarget = tmp;
			
			observer.disconnect();
			
			if (obsTarget !== null) {
				obsTarget.querySelectorAll("li")
					.forEach(
						function (node) {
							doProcessNode(node);
						}
					);
				
				observer.observe(
					obsTarget,
					
					{
						childList: true
					}
				);
			}
		};
	
	doMain();
})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元