Tinychat Enhancement Suite

Fixes some Tinychat beta room annoyances and adds useful features.

当前为 2017-09-08 提交的版本,查看 最新版本

// ==UserScript==
// @name         Tinychat Enhancement Suite
// @namespace    https://greasyfork.org/en/users/27283-mutationobserver
// @version      2017-09-08--5
// @description  Fixes some Tinychat beta room annoyances and adds useful features.
// @author       MutationObserver
// @match        https://tinychat.com/room/*
// @exclude      https://tinychat.com/room/*?1
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues

// ==/UserScript==
/* jshint -W097 */
//'use strict';

var webappElem = document.querySelector("tinychat-webrtc-app").shadowRoot;
var chatlogElem = webappElem.querySelector("tinychat-chatlog").shadowRoot;
var titleElem = webappElem.querySelector("tinychat-title").shadowRoot;
var sidemenuElem = webappElem.querySelector("tinychat-sidemenu").shadowRoot;
var videolistElem = webappElem.querySelector("tinychat-videolist").shadowRoot;

var chatlistElem = sidemenuElem.querySelector("tinychat-chatlist").shadowRoot;
var userlistElem = sidemenuElem.querySelector("tinychat-userlist").shadowRoot;

var chatlogCSS = chatlogElem.querySelector("#chat-wrapper");
var sidemenuCSS = sidemenuElem.querySelector("label.switcher");
var webappCSS = webappElem.querySelector("#toast");
var chatlistCSS = chatlistElem.querySelector("#header > span");
var userlistCSS = userlistElem.querySelector("#header > span");
var titleCSS = titleElem.querySelector("#room-header");
var videolistCSS = videolistElem.querySelector("#videolist");

var audioPop = new Audio('/webrtc/2.0.20-420/sound/pop.mp3');

var giftsElemWidth = 150;

function waitForSettings() {
	settingsVisible = false;
	if (titleElem.querySelector("#room-header-gifts") != null) {
		clearInterval(settingsWaitInterval);
		
		giftsElemWidth = titleElem.querySelector("#room-header-gifts").offsetWidth;
		if (titleElem.querySelector("#room-header-gifts-items") == null) {
			giftsElemWidth1000 = giftsElemWidth + 45;
		}
		else { giftsElemWidth1000 = giftsElemWidth; }
		titleElem.querySelector("#titleCSS").innerHTML += `
			#tes-settings {
				right: ` + giftsElemWidth + `px;
			}
			@media screen and (max-width: 1000px) {
				#tes-settings {
					right: ` + giftsElemWidth1000 + `px;
				}
			}
		`;
		
		settingsElem = titleElem.querySelector("#room-header-gifts").insertAdjacentHTML("beforebegin", `
		<div id="tes-settings">
			<div id="tes-settingsBox" class="hidden">
				<p id="title"><a href="https://greasyfork.org/en/scripts/32964-tinychat-enhancement-suite" target="_blank">Tinychat Enhancement Suite</a></p>
				<div id="tes-settings-mentions" class="tes-setting-container">
					<span class="label">
						Alert phrases
						<span class="info" data-title='A comma-separated list of phrases you would like alerts and highlights for. Example of 3 phrases: "hello,Google Chrome,sky"'>?</span>
					</span>
					<input><button class="save">save</button>
				</div><!--
				<div id="tes-settings-font" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						Improve font
						<span class="info" data-title='The default font is too thin some browsers'>?</span>
					</span>
				</div>
				<div id="tes-settings-messageanim" class="tes-setting-container">
					<input type="checkbox">
					<span class="label">
						No message animation
					</span>
				</div>-->
			</div>
			<div id="tes-settingsGear" title="Tinychat Enhancement Suite settings"><span>✱</span></div>
		</div>
		`);
		
		titleElem.getElementById("tes-settingsGear").addEventListener("click", toggleSettings);
		titleElem.querySelector("#tes-settings #tes-settings-mentions button.save").addEventListener("click", function(){mentionsManager("save");} );
		settingMentions = [];
		mentionsManager("load");
	}
}

function toggleSettings() {
	if (settingsVisible == true) {
		titleElem.getElementById("tes-settingsBox").classList.add("hidden");
		titleElem.getElementById("tes-settings").classList.remove("tes-open");
		settingsVisible = false;
	}
	
	else {
		titleElem.getElementById("tes-settingsBox").classList.remove("hidden");
		titleElem.getElementById("tes-settings").classList.add("tes-open");
		settingsVisible = true;
	}
	
}

var settingsWaitInterval = setInterval(waitForSettings,1000);


function mentionsManager(mode) {
	var inputElem = titleElem.querySelector("#tes-settings #tes-settings-mentions input");
	// phrases = inputElem.value.split(",");
	var phrase = inputElem.value;
	
	if (mode == "save") {
		GM_setValue("tes-Mentions", phrase);
		settingMentions = phrase.split(",");
	}
	if (mode == "load") {
		var loadedMentions = GM_getValue("tes-Mentions");
		inputElem.value = loadedMentions;
		settingMentions = loadedMentions.split(",");
	}
						 return;
	
	var phrase = phrase.toString();
	if (mode == "save") {
		settingMentions.push(phrases);
		GM_setValue("tes-Mentions", JSON.stringify(setting_Mentions));
		console.log("Mention add:" + phrases);
	}
	if (mode == "load") {
		var mentions = JSON.parse(GM_getValue("tes-Mentions"));
		console.log("Mention load:" + mentions);
		settingMentions = mentions;
		inputElem.value = settingMentions.join();
	}
}

injectCSS();
function injectCSS(cssName) {
	// if (cssName == "titleCSS")
	titleCSS.insertAdjacentHTML("beforeend", `
	<style id="titleCSS">
		@keyframes ease-to-left {
			0% {right: -50px; opacity: 0;}
			100% {right: 0; opacity: 1;}
		}
		@keyframes ease-to-right {
			0% {right:auto;}
			100% {right:0;}
		}
		@keyframes ease-to-bottom-21px {
			0% {top:-300px; opacity: 0;}
			100% {top:0; opacity: 1;}
		}
		#tes-settings > div {
			/*animation: ease-to-bottom-21px .2s ease 0s 1;*/
			position: relative;
			top: 0;
			height: 100px;
		}
		@media screen and (max-width: 1000px) {
			#tes-settings > div {
				height: 92px;
			}
		}
		#tes-settings .hidden { display: none; }
		#tes-settings #title { font-weight: bold; }
		#tes-settings {
			animation: ease-to-bottom-21px .2s ease 0s 1;
			/*max-height: 10%;*/
			margin-right: 15px;
			font-size: 11px;
			flex: none;
			overflow: hidden;
			z-index: 9;
			position: absolute;
			top: 13px;
			right: ` + (giftsElemWidth + 10).toString() + `px;
		}
		@media screen and (max-width: 1000px) {
			#tes-settings {
				right: ` + (giftsElemWidth + 40).toString() + `px;
			}
		}
		#tes-settings:hover {
			overflow: visible;
		}
		#tes-settingsGear {
			font-size: 70px;
			color: #38cd57;
			color: #53b6ef;
			float: right;
		}
		#tes-settingsGear:hover {
			cursor: pointer;
			color: #7ccefe;
		}
		.tes-open #tes-settingsGear {
			background: white;
			border-bottom-right-radius: 15px;
			border-top-right-radius: 15px;
			border: #ddd 1px solid;
			border-left: 0;
			/*transition: all .2s linear;*/
			}
		#tes-settingsGear span {
			display: block;
			transition: transform 0.4s ease-in-out;
		}
		.tes-open #tes-settingsGear span {
			transform: rotate(-90deg);
		}		
		#tes-settingsBox {
			background: white;
			padding: 0px 10px 0px 10px;
			float: left;
			border: #53b6ef 1px solid;
			border-top-left-radius: 12px;
			border-bottom-left-radius: 12px;
			animation: ease-to-left .2s ease 0s 1;
			right: 0;
		}
		#tes-settingsBox.hidden {
			animation: ease-to-right .2s ease 0s 1;
			display: visible;
			/*position: relative; right: -1000px;*/
		}
		
		/*** Inline with header ***/
		#tes-settingsBox {
			border-bottom: 0;
			border-top-left-radius: 0px;
			border-bottom-left-radius: 0px;
		}
		#tes-settings {
			top: -1px;
		}
		#tes-settingsGear {
			display: table;
		}
		#tes-settingsGear span {
			display: table-cell;
			vertical-align: middle;
		}
		/*** *************   ***/
		
		#tes-settings .tes-setting-container input[type=checkbox] {
			margin: 0;
			margin-right: 1px;
			float: left;
		}
		#tes-settings .label{
			margin-right: 4px;
		}
		#tes-settings .info{
			margin-left: 3px;
			color: #0d94e3;
			font-weight: bold;
			border: #0d94e3 1px solid;
			border-radius: 16px;
			height: 1em;
			width: 1em;
			text-align: center;
			display: inline-block;
		}
		#tes-settings .info:hover:after{
			font-weight: normal;
			padding: 4px 7px 4px 7px;
			border-radius: 7px;
			color: white;
			background: #61787f;
			content: attr(data-title);
			display: inline-block;
			position: absolute;
			top: 52px;
			left: 0;
		}
		/*#tes-settings .label:hover:before{
			border: solid;
			border-color: #61787f transparent;
			border-width: 0px 6px 6px 6px;
			top: 10px;
			content: "";
			left: 8%;
			position: relative;
			display: inline-block;
		}*/

		#tes-settings a:visited, #tes-settings a:link {
			text-decoration: none;
			color: inherit;
		}
		#tes-settings a:hover {
			color: #53b6ef;
		}

		#room-header {
			min-height: 10%; /* 105px */
			max-height: 10%;
		}
		@media screen and (max-width: 600px) {
			#room-header {
				min-height: inherit;
				max-height: inherit;
			}
		}
		#room-header-info {
			padding: 0;
		}
		#room-header-info-text {
			height: auto;
		}
		@media screen and (max-width: 600px) {
			#room-header-info-text {
				height: inherit;
			}
		}
		#room-header-avatar {
			margin: 2px 10px 0 35px;
			height: 90px;
			min-width: 90px;
			max-width: 90px;
		}
		#room-header-avatar:hover {
			border-radius: unset;
		}
		#room-header-gifts {
			padding: 10px 10px;
		}
	</style>
	`);

	videolistCSS.insertAdjacentHTML("beforeend", `
	<style id="videolistCSS">
		#videos-header {
			height: 10px;
			min-height: 10px;
		}
		#Fvideolist * {
			width: 75%!important;
			display: contents;
			float: right;
			flex-direction: column;
		}
		#Fvideos {
			flex-direction: unset;
			flex-wrap: unset;
		}
	</style>
	`);


	chatlistCSS.insertAdjacentHTML("afterend", `
	<style id="chatlistCSS">
		#chatlist > div > span {
			padding-left: 1%;
		}
	</style>
	`);

	userlistCSS.insertAdjacentHTML("afterend", `
	<style id="userlistCSS">
		#userlist > div > span {
			padding-left: 1%;
		}
		.list-item > span > span {
			right: auto;
			padding: 0 5px;
		}
		.list-item > span > .nickname {
			padding-right: 3px;
		}
		.list-item > span > img,
		.list-item > span[data-status="extreme"]:before,
		.list-item > span[data-status="gold"]:before,
		.list-item > span[data-status="pro"]:before {
			left: auto;
			right: 0;
		}
		.list-item > span > span[data-moderator="1"]:before {
			filter: hue-rotate(226deg) saturate(4000%);
	}
	</style>
	`);

	document.querySelector("body").insertAdjacentHTML("beforeend", `
	<style id="bodyCSS">
		#nav-static-wrapper {
			width: 2px;
			opacity: .7;
		}
		@media screen and (max-width: 1000px) {
			#nav-static-wrapper {
				width: 82px;
				opacity: 1;
			}
		}
	   #content {
		   padding: 0;
	   }
		body {
		  font-family: sans-serif;
		}
		#header-user {
			left: 115px;
		}
		@media screen and (max-width: 1000px) {
			#header-user {
				left: 21px;
			}
		}
		@media screen and (max-width: 600px) {
			#header-user {
				left: auto;
				right: 54px;
			}
		}
	</style>
	`);


	messageCSS = `
		.tes-mention-message { color: red; }
	`;

	chatlogCSS.insertAdjacentHTML("beforeend", `
	<style id="chatlogCSS">

		#chat-content > .message {
			padding-bottom: 0;
			padding-top: 0!important;
			margin-bottom: 0;
			min-height: 0px!important;
		}
		/*
		#chat-content > .message:hover {
			background: rgba(0, 0, 0, 0.03);
		}
		*/
		#chat-content > .message.common {
			margin-bottom: 5px;
		}
		#chat-content > .message.system {
			padding: 0;
		}
		#chat-instant > a:first-child,
		#chat-content > .message > a:first-child {
			top: auto;
		 }
		#chat-position #input:before {
			background: none;
		}
		#chat-instant > a > .avatar,
		#chat-content > .message > a > .avatar {
			border-radius: unset;
		}
		#timestamp {
			font-size: 11px;
			color: silver;
			float: right;
		}
		#chat-content > .message > .nickname {
			overflow: initial;
			line-height: initial;
		}
		#chat-content div.message.common:last-of-type {
			margin-bottom: 10px;
		}
	</style>
	`);

	sidemenuCSS.innerHTML += `
	<style id="sidemenuCSS">
		#sidemenu {
			min-width: 162px;
			max-width: 10%;
			left: auto;
		}
		@media screen and (max-width: 1000px) {
			#sidemenu {
				left: -188px;
			}
		}
		#sidemenu-content {
			padding-left: 1%;
		}
		#live-directory-wrapper {
			padding: 0;
		}
		#user-info {
			padding: 0 3px;
			height: auto;

		}
	</style>
	`;

	webappCSS.innerHTML += `
	<style id="webappCSS">
		#room {
			padding: 0;
			padding-left: 142px;
		}
		@media screen and (max-width: 1000px) {
			:host > #room {
				padding-left: 82px;
			}
		}
		@media screen and (max-width: 600px) {
			:host > #room {
				padding-left: 0;
			}
		}
	</style>
	`;
}

var scrollbox = chatlogElem.querySelector("#chat");
var unreadbubble = chatlogElem.querySelector("#input-unread");
var autoScrollStatus = true;

function updateScroll() {
	scrollbox.scrollTop = scrollbox.scrollHeight;
	scrollbox.scrollTop = scrollbox.scrollTop + 5;
}

function userHasScrolled(e) {
	var scrollwheelAmount = e.deltaY;

	if (scrollwheelAmount < 0) {
		autoScrollStatus = false;
	}
	if (autoScrollStatus === false && scrollbox.scrollHeight - scrollbox.scrollTop == scrollbox.offsetHeight) {
		autoScrollStatus = true;
	}
}

function newMessageAdded() {
	if (autoScrollStatus === true) { updateScroll(); }
	timestampAdd();
	messageParser();
}

messageCount = 0;
function messageParser() {
	latestMessageElem = chatlogElem.querySelector("#chat-content div.message.common:last-of-type");
	if (latestMessageElem != null) {
		latestMessageElem.setAttribute("id", "msg-"+messageCount);
		messageCount++;

		tcMessageHtmlElem = latestMessageElem.querySelector("tc-message-html").shadowRoot;
		latestMessageContentElem = tcMessageHtmlElem.querySelector("#html");
		
		var node = document.createElement("style");
		var textnode = document.createTextNode(messageCSS);
		node.appendChild(textnode);
		tcMessageHtmlElem.appendChild(node);
		
		
		latestMessageContent = latestMessageContentElem.innerHTML;
		
		for (i=0; i < settingMentions.length; i++) {
			if (latestMessageContent.toLowerCase().includes(settingMentions[i].toLowerCase())) {
				latestMessageContentElem.classList.add("tes-mention-message");
				audioPop.play();
				break;
			}
		}
	}
}

var x = new MutationObserver(function (e) {
  if (e[0].addedNodes) newMessageAdded();
});
x.observe(chatlogElem.querySelector("#chat-content"), { childList: true });


var x = new MutationObserver(function (e) {
  if (e[0].addedNodes) newCamAdded();
});
x.observe(videolistElem.querySelector(".videos-items:last-child"), { childList: true });


var scrollboxEvent = scrollbox.addEventListener("wheel", userHasScrolled);
var unreadbubble = unreadbubble.addEventListener("click", function(){autoScrollStatus = true;} );

function timestampAdd() {
	var queryString = "#chat-content div.message.common:last-of-type .nickname";
	
	var SHOW_SECONDS = true;

	var date = new Date(); 
	var hours = date.getHours();
	var minutes = date.getMinutes().toString();
	var secs = date.getSeconds().toString();

	if (hours > 12) {
		hours = (hours % 12 || 12);
		var period = "pm";
	}
	else { var period = "am"; }

	if (hours == "0") { hours = "12"; }
	if (minutes == "0") { minutes = "00"; }
	if (minutes.length == 1) { minutes = "0" + minutes; }
	if (secs.length == 1) { secs = "0" + secs; }

	if (SHOW_SECONDS == true) {
		var timestamp = hours + ":" + minutes + ":" + secs + "" + period;
	}
	else {
		var timestamp = hours + ":" + minutes + period;
	}
	
	if (chatlogElem.querySelector(queryString) != null) {
		var recentMsgNickname = chatlogElem.querySelector(queryString);
		recentMsgNickname.insertAdjacentHTML("afterend", "<span id='timestamp'> " + timestamp + "</span>");	
	}
}

// var newCamAddedInterval = setInterval(newCamAdded, 1000);
// var camAddedCounter = 0;
// newCamAdded();
function newCamAdded() {
	return;
	// camAddedCounter += 1;
	// if (camAddedCounter == 10) {
	// 	clearInterval(newCamAddedInterval);
	// }
	console.log("Cam added");
	var queryString = ".videos-items:last-child > .js-video";
	if (videolistElem.querySelector(queryString) != null) {
		var camElems = videolistElem.querySelectorAll(queryString);
		// console.log("found");
	}
	else {
		return;
	}
	for (i=0; i < camElems.length; i++) {
		if ( camElems[i].hasAttribute("id") == true ) { continue; }
		var camItem = camElems[i].querySelector("tc-video-item").shadowRoot;
		var camName = camItem.querySelector(".nickname").getAttribute("title");
		camElems[i].setAttribute("id", "camUser-"+camName);
		console.log("Cam: " + camName);
	}
}