Sleazy Fork is available in English.

Chaturbate Enhancer

Enhances Chaturbate by adding multiple new features.

As of 10.05.2023. See ბოლო ვერსია.

// ==UserScript==
// @name             Chaturbate Enhancer
// @name:de          Chaturbate Enhancer
// @name:es          Chaturbate Enhancer
// @name:es-CO       Chaturbate Enhancer
// @name:it          Chaturbate Enhancer
// @name:fr          Chaturbate Enhancer
// @name:fr-CA       Chaturbate Enhancer
// @name:ru          Chaturbate Enhancer
// @name:tr          Chaturbate Enhancer
// @name:ro          Chaturbate Enhancer
// @name:no          Chaturbate Enhancer
// @name:nl          Chaturbate Enhancer
// @name:pl          Chaturbate Enhancer
// @name:ja          Chaturbate Enhancer
// @name:el          Chaturbate Enhancer
// @name:hu          Chaturbate Enhancer
// @name:fi          Chaturbate Enhancer
// @name:ar          Chaturbate Enhancer
// @name:hi          Chaturbate Enhancer
// @name:id          Chaturbate Enhancer
// @name:ko          Chaturbate Enhancer
// @name:pt-PT       Chaturbate Enhancer
// @name:pt-BR       Chaturbate Enhancer
// @name:zh          Chaturbate Enhancer
// @name:zh-CN       Chaturbate Enhancer
// @name:zh-TW       Chaturbate Enhancer
// @name:cs          Chaturbate Enhancer
// @name:sk          Chaturbate Enhancer
// @name:sl          Chaturbate Enhancer
// @name:sv          Chaturbate Enhancer
// @name:sr          Chaturbate Enhancer
// @name:af          Chaturbate Enhancer
// @name:sq          Chaturbate Enhancer
// @name:hy          Chaturbate Enhancer
// @name:be          Chaturbate Enhancer
// @name:bg          Chaturbate Enhancer
// @name:da          Chaturbate Enhancer
// @name:et          Chaturbate Enhancer
// @name:he          Chaturbate Enhancer
// @name:hr          Chaturbate Enhancer
// @name:fa          Chaturbate Enhancer
// @name:ur          Chaturbate Enhancer
// @name:bn          Chaturbate Enhancer
// @name:th          Chaturbate Enhancer
// @name:eo          Chaturbate Enhancer
// @name:ug          Chaturbate Enhancer
// @name:vi          Chaturbate Enhancer
// @description      Enhances Chaturbate by adding multiple new features.
// @description:de   Verbessert Chaturbate durch Hinzufügen mehrerer neuer Funktionen.
// @description:es   Mejora Chaturbate al agregar múltiples funciones nuevas.
// @description:es-CO Mejora Chaturbate al agregar múltiples funciones nuevas.
// @description:it   Migliora Chaturbate aggiungendo più nuove funzionalità.
// @description:fr   Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
// @description:fr-CA Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
// @description:ru   Улучшает Chaturbate, добавляя несколько новых функций.
// @description:tr   Birden çok yeni özellik ekleyerek Chaturbate'i geliştirir.
// @description:ro   Îmbunătățește Chaturbate prin adăugarea de mai multe funcții noi.
// @description:no   Forbedrer Chaturbate ved å legge til flere nye funksjoner.
// @description:nl   Verbetert Chaturbate door meerdere nieuwe functies toe te voegen.
// @description:pl   Ulepsza Chaturbate, dodając wiele nowych funkcji.
// @description:ja   複数の新機能を追加して Chaturbate を強化します。
// @description:el   Βελτιώνει το Chaturbate προσθέτοντας πολλές νέες δυνατότητες.
// @description:hu   Több új funkció hozzáadásával továbbfejleszti a Chaturbate szolgáltatást.
// @description:fi   Parantaa Chaturbatea lisäämällä useita uusia ominaisuuksia.
// @description:ar   يعزز Chaturbate عن طريق إضافة ميزات جديدة متعددة.
// @description:hi   कई नई सुविधाओं को जोड़कर Chaturbate को बेहतर बनाता है।
// @description:id   Meningkatkan Chaturbate dengan menambahkan beberapa fitur baru.
// @description:ko   여러 새로운 기능을 추가하여 Chaturbate를 향상시킵니다.
// @description:pt-PT Aprimora o Chaturbate adicionando vários novos recursos.
// @description:pt-BR Aprimora o Chaturbate adicionando vários novos recursos.
// @description:zh   通过添加多个新功能来增强 Chaturbate。
// @description:zh-CN 通过添加多个新功能来增强 Chaturbate。
// @description:zh-TW 通过添加多个新功能来增强 Chaturbate。
// @description:cs   Vylepšuje Chaturbate přidáním několika nových funkcí.
// @description:sk   Vylepšuje Chaturbate pridaním viacerých nových funkcií.
// @description:sl   Izboljša Chaturbate z dodajanjem več novih funkcij.
// @description:sv   Förbättrar Chaturbate genom att lägga till flera nya funktioner.
// @description:sr   Побољшава Цхатурбате додавањем више нових функција.
// @description:af   Verbeter Chaturbate deur verskeie nuwe kenmerke by te voeg.
// @description:sq   Përmirëson Chaturbate duke shtuar veçori të shumta të reja.
// @description:hy   Ընդլայնում է Chaturbate-ը՝ ավելացնելով բազմաթիվ նոր հնարավորություններ:
// @description:be   Паляпшае Chaturbate шляхам дадання некалькіх новых функцый.
// @description:bg   Подобрява Chaturbate чрез добавяне на множество нови функции.
// @description:da   Forbedrer Chaturbate ved at tilføje flere nye funktioner.
// @description:et   Täiustab Chaturbate'i, lisades mitu uut funktsiooni.
// @description:he   משפר את Chaturbate על ידי הוספת תכונות חדשות מרובות.
// @description:hr   Poboljšava Chaturbate dodavanjem više novih značajki.
// @description:fa   Chaturbate را با افزودن چندین ویژگی جدید تقویت می کند.
// @description:ur   متعدد نئی خصوصیات شامل کرکے Chaturbate کو بہتر بناتا ہے۔
// @description:bn   একাধিক নতুন বৈশিষ্ট্য যোগ করে Chaturbate উন্নত করে।
// @description:th   ปรับปรุง Chaturbate ด้วยการเพิ่มคุณสมบัติใหม่หลายอย่าง
// @description:eo   Plibonigas Chaturbate aldonante plurajn novajn funkciojn.
// @description:ug   كۆپ خىل يېڭى ئىقتىدارلارنى قوشۇش ئارقىلىق Chaturbate نى كۈچەيتىدۇ.
// @description:vi   Cải thiện Chaturbate bằng cách thêm nhiều tính năng mới.
// @version          3.0.0
// @author           improper.dev
// @license          CC-BY-ND-4.0
// @copyright        MoonDivision (https://sleazyfork.org/en/users/884016-improper-dev)
// @namespace        https://sleazyfork.org/en/users/884016-improper-dev
// @homepage         https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer
// @supportURL       https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer/feedback
// @contributionURL  https://cb-enh.improper.dev/contribute
// @icon             https://www.google.com/s2/favicons?sz=32&domain=chaturbate.com
// @icon64           https://www.google.com/s2/favicons?sz=64&domain=chaturbate.com
// @match            https://chaturbate.com/*
// @match            https://*.chaturbate.com/*
// @connect          camschedule.com
// @connect          onechance.onelove.workers.dev
// @connect          cb-enh.improper.dev
// @connect          cb-enh-thumb.improper.dev
// @grant            GM_addStyle
// @grant            GM_addElement
// @grant            GM_xmlhttpRequest
// @grant            GM_registerMenuCommand
// @grant            GM_unregisterMenuCommand
// @grant            GM_setClipboard
// @require          https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
// @require          https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js
// @run-at           document-start
// @noframes
// ==/UserScript==

(function() {
'use strict';

// webext compat start
if(typeof window.unsafeWindow === 'undefined') {
	window.unsafeWindow = window;
}

if(unsafeWindow.___cbEnhancer___) {
	return;
}
unsafeWindow.___cbEnhancer___ = true;

function addStyle(style) {
	if(typeof GM_addStyle !== 'undefined') {
		return GM_addStyle(style);
	}

	let styleEl = document.createElement('style');
	styleEl.textContent = style;
	document.head.appendChild(styleEl);
}

function addElement(parent_node, tag_name, attributes) {
	if(typeof GM_addElement !== 'undefined') {
		return GM_addElement(parent_node, tag_name, attributes)
	}

	let id = Date.now();
	let msgData = {
		'msg-name': 'cb-enh-add-element',
		'data': {
			'parent-node': id,
			'tag-name': tag_name,
			'attributes': attributes
		}
	};
	$(parent_node).addClass('cb-enh-add-element-id-' + id);
	window.postMessage(JSON.stringify(msgData), '*');
}

let xhrTasks = [];
function xmlhttpRequest(details) {
	if(typeof GM_xmlhttpRequest !== 'undefined') {
		return GM_xmlhttpRequest(details);
	}

	let id = xhrTasks.length;
	xhrTasks[id] = details;

	let msgData = {
		'msg-name': 'cb-enh-xhr',
		'data': {
			'xhr-id': id,
			'details': details
		}
	};

	window.postMessage(JSON.stringify(msgData), '*');
}

window.addEventListener('message', function(e) {
	let msgData;
	try {
		msgData = JSON.parse(e.data);
	}
	catch(e) {
		return;
	}

	if(!'msg-name' in msgData || msgData['msg-name'] != 'cb-enh-xhr-event') {
		return;
	}

	if(!'data' in msgData || !'data' in msgData['data']) {
		return;
	}

	let id = msgData['data']['xhr-id'];
	if(!id in xhrTasks) {
		return;
	}

	let event_name = msgData['data']['event-name'];
	let event = msgData['data']['event'];
	event.responseText = msgData['data']['responseText'];

	if(!xhrTasks[id][event_name]) {
		return;
	}

	xhrTasks[id][event_name](event);
});

function registerMenuCommand(name, callback, accessKey) {
	if(typeof GM_registerMenuCommand !== 'undefined') {
		return GM_registerMenuCommand(name, callback, accessKey);
	}

	// @todo
}

function unregisterMenuCommand(menuCmdId) {
	if(typeof GM_unregisterMenuCommand !== 'undefined') {
		return GM_unregisterMenuCommand(menuCmdId);
	}

	// @todo
}

function setClipboard(data, info) {
	if(typeof GM_setClipboard !== 'undefined') {
		return GM_setClipboard(data, info);
	}

	// @todo
}
// webext compat end

let intvWaitBody = null;
let intvWaitVideo = null;
let intvUpdateAvatarInPrivBoard = null;
let intvUpdateFollowedList = null;
let gIntvCheckIfRoomIsOnline = null;

let gSettings = {};
let gLocales = {};
let currentHoverInterval = null;
let lastLoadedThumbReqTime = 0;
let gCurrentBroadcaster = null;

let gVideoControlsModalShown = false;

let gCapturingScreenshot = false;

let gRecording = false;
let gRecordingStream = null;
let gRecordingCanceledByUser = false;

let gIsInMultiView = false;

function getCookie(name) {
	let nameEQ = encodeURIComponent(name) + "=";
	let ca = document.cookie.split(';');
	for(let i = 0; i < ca.length; i++) {
		let c = ca[i];
		while(c.charAt(0) === ' ') {
			c = c.substring(1, c.length);
		}

		if(c.indexOf(nameEQ) === 0) {
			return decodeURIComponent(c.substring(nameEQ.length, c.length));
		}
	}
	return null;
}

function doNeedDarkMode() {
	return getCookie('theme_name') === null && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}

function enableDarkMode() {
	// Automatically enable site dark mode if system has dark mode enabled
	if(document.body && doNeedDarkMode()) {
		$('body').addClass('darkmode');
		document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
		document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
	}
}

if(doNeedDarkMode()) {
	if(document.body) {
		enableDarkMode();
	}
	else {
		intvWaitBody = setInterval(function() {
			if(document.body) {
				enableDarkMode();
				clearIntervalEx(intvWaitBody);
			}
		}, 10);
	}
}

function isMultiViewPage() {
	return window.location.pathname === '/' && (window.location.hash === '#multicam' || window.location.hash.startsWith('#multicam?rooms='));
}

if(isMultiViewPage()) {
	addStyle(`
		.content {
			display: none !important;
		}
	`);
}

let style = `
/* Hide media overlays */
.photoVideoDetailSection img {
	filter: unset !important;
}

.userUpload div {
	background: none !important;
}

.psContainer .lockOverlayBg, .smContainer .lockOverlayBg {
	display: none !important;
}

.userUpload img[src$="lock.svg"] {
	display: none !important;
}

/* Hide ads */
.ad, .vote-banner {
	display: none !important;
}

.cb-enh-avatar {
	margin-left: 10px;
	border: 1px solid #bfbfbf;
	width: 150px;
	height: 150px;
	background-color: #ebebeb;
	margin-bottom: 5px;
	background-size: 100% 100%;
	position: relative;
}

.darkmode .cb-enh-avatar {
	border-color: #2d3e50;
	background-color: #202c39;
}

.cb-enh-avatar, .cb-enh-avatar img {
	border-radius: 150px;
}

.cb-enh-avatar img {
	width: 100%;
	height: 100%;
	opacity: 0;

	position: absolute;
	left: 0;
	top: 0;

	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	user-drag: none;
	app-region: no-drag;

	pointer-events: none;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.cb-enh-offline-snapshot {
	display: block;
	border-radius: 4px;
	margin-top: 10px;
}

.cb-enh-footer {
	font-size: 14px;
	color: #341b00;
	font-weight: bold;
}

.darkmode .cb-enh-footer {
	color: #efefef;
}

.cb-enh-footer a {
	color: inherit !important;
	text-decoration: underline;
}

/* Enlarge media in bio */
tr:not(.smContainer) .contentText .previewBorder {
	width: 190px;
	height: 135px;
}

tr:not(.smContainer) .contentText .tokenText {
	top: 118px !important;
	right: 5px !important;
}

/* Detach floaters in "about" */
tr:not(.smContainer):not(.psContainer) .contentText img, tr:not(.smContainer):not(.psContainer) .contentText li, tr:not(.smContainer):not(.psContainer) .contentText a, tr:not(.smContainer):not(.psContainer) .contentText p, tr:not(.smContainer):not(.psContainer) .contentText span {
	position: unset !important;
}

/* Disable custom backgrounds in bio */
tr:not(.smContainer):not(.psContainer) .contentText * {
	background: unset !important;
}

/* Disable custom cursor in bio */
tr:not(.smContainer):not(.psContainer) .contentText * {
	cursor: auto !important;
}

tr:not(.smContainer):not(.psContainer) .contentText a {
	cursor: pointer !important;
}

tr:not(.smContainer):not(.psContainer) .contentText a * {
	cursor: pointer !important;
}
/**/

.cb-enh-video {
	max-width: 900px;
	margin: 0px;
	padding: 0px;
	width: 100%;
	height: 100%;
	object-fit: contain;
	background-color: rgba(0, 0, 0, 0);
	display: inline;
	border: 0;
	outline: 0;
	border-radius: 4px;
}

.cb-enh-video::-webkit-media-controls-play-button {
	display: none;
}

.cb-enh-video::-webkit-media-controls-timeline {
	display: none;
}

.cb-enh-video::-webkit-media-controls-current-time-display {
	display: none;
}

.cb-enh-video::-webkit-media-controls-timeline-container {
	display: none;
}

.cb-enh-video::-webkit-media-controls-time-remaining-display {
	display: none;
}

.cb-enh-schedule-frame {
	width: 100%;
	height: 350px;
	border: 0;
}

.cb-enh-chat-frame {
	width: 100%;
	max-width: 1400px;
	height: 700px;
	border: 0;
	border-radius: 4px;
}

#cb-enh-inac-load-chat {
	cursor: pointer;
}

.noselect {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.cb-enh-video-bar-btn {
	height: 15px;
	width: auto;
	position: relative;
	overflow: hidden;
	-webkit-tap-highlight-color: transparent;
	font-family: UbuntuMedium, Helvetica, Arial, sans-serif;
	font-size: 12px;
	padding: 3px 8px 2px;
	top: -4px;right: 1px;
	float: right;
	border-radius: 3px;
	cursor: pointer;
	margin-right: 5px;
	background-color: #880471;
	color: white;
	text-transform: uppercase;
}

.cb-enh-tab-bar-modal {
	width: 500px;
	border-width: 1px;
	position: absolute;
	border-style: solid;
	border-radius: 4px;
	font-size: 14px;
	padding: 8px 0px 8px 8px;
	display: none;
	z-index: 5;
	line-height: 22px;
	box-shadow: rgba(0, 0, 0, 0.08) 0px 4px 16px;
}

.cb-enh-tab-bar-modal-arrow-down {
	position: absolute;
	width: 0;
    height: 0;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
    border-top: 10px solid #0554a3;
}

.videoPlayerDiv video.cb-enh-video-mirrored {
	transform: scale(-1, 1) !important;
}

.videoPlayerDiv video.cb-enh-video-inverted {
	transform: scale(1, -1) !important;
}

.videoPlayerDiv video.cb-enh-video-mirrored.cb-enh-video-inverted {
	transform: scale(-1, -1) !important;
}

/* fix mirroring/inverting video doesn't work when video is fullscreen */
.videoPlayerDiv:not([style*='height: 100%; width: 100%;']) video.cb-enh-video-mirrored, .videoPlayerDiv:not([style*='height: 100%; width: 100%;']) video.cb-enh-video-inverted {
	width: calc(100% - 10px) !important;
}

.vjs-fullscreen video.cb-enh-video-mirrored, .vjs-fullscreen video.cb-enh-video-inverted {
	width: calc(100% - 10px) !important;
}

.cb-enh-video-controls-modal-btns {
	margin-top: 5px;
}

.cb-enh-vid-control-slider {
	width: 50%;
}

#cb-enh-video-controls-record {
	background-color: #090;
}

#cb-enh-video-controls-record.cb-enh-active {
	background-color: #ff0000;
}

/* Do not display entrance terms overlay */
.entrance-terms--shown {
	position: inherit !important;
	top: inherit !important;
	left: inherit !important;
	right: inherit !important;
	bottom: inherit !important;
	overflow: inherit !important;
	background-color: #fff;
	visibility: inherit !important;
}

#entrance_terms_overlay {
	display: none !important;
}

/* Acc info box */
#cb-enh-acc-info {
	width: 500px;
	height: 69px;
	box-sizing: border-box;
	font-size: 15px;
	overflow: hidden;
	display: inline-block;
	vertical-align: top;
	margin: 0px;
	float: right;

	color: #292929;
}

.darkmode #cb-enh-acc-info {
    color: #f2f2f2;
}

.cb-enh-acc-info-outer {
	position: relative;
	height: 100%;
}

.cb-enh-acc-info-inner {
	margin: 0;
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	right: 0%;
	margin-right: 10px;
	cursor: pointer;
}

.blurred-login-overlay > div > span, .blurred-login-overlay > div > hr {
	display: none;
}

.thumbnail_label {
	pointer-events: none;
	text-transform: uppercase;
}

/* add outline to text in interactive fullscreen/theater mode */
/* and noselect */
div[data-paction="TheaterOverlayTabs"] span {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* add outline to text in interactive fullscreen/theater mode (selected quality text) */
/* and noselect */
span[title="Video Quality"] {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* add outline to text in interactive fullscreen/theater mode (quality selector qualities) */
div#TheaterModePlayer div[ts="r"] div:not([style*="color: rgb(51, 51, 51);"]) {
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

div.slider[title="Volume Slider"] div:nth-child(1) {
	border-top: 1px solid;
	border-right: 1px solid;
	border-bottom: 1px solid;
	border-color: #1c1c1c;
}

div.slider[title="Volume Slider"] div:nth-child(2) {
	border-top: 1px solid;
	border-left: 1px solid;
	border-bottom: 1px solid;
	border-color: #1c1c1c;
}

div.slider[title="Volume Slider"] div:nth-child(3) {
	border: 1px solid #1c1c1c;
}

/* and noselect */
div.slider[title="Volume Slider"] div {
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* styling for hover are for video controls in theater/interactive fullscreen mode */
#cb-enh-theater-controls-area {
	position: absolute;
	width: 100%;
	heigth: 50px;
}

/* make video controls theater/interactive fullscreen mode appear when bottom of video is hovered */
div[data-paction="VideoControl"] {
	display: block !important;
	opacity: 1;
}

div[data-paction="VideoControl"][style*="display: none;"] {
	opacity: 0;
}

/* make quality selector slighty taller so scrollbar won't appear */
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
	max-height: 20em !important;
}

/* make quality selector nicely rounded */
div.vjs-menu-button.vjs-menu-button-popup.vjs-control.vjs-button .vjs-menu-content {
	border-radius: 8px 8px 0 0;
}

#TheaterModePlayer #volume-high, #TheaterModePlayer #volume-mute {
	-webkit-user-drag: none;
	-webkit-app-region: no-drag;
	user-drag: none;
	app-region: no-drag;

	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* styling for grid size selector */
ul.sub-nav.genderTabs {
	float: left;
}

.cb-enh-grid-size-selector-img {
	float: right;
	position: relative;
	margin: 0;
	display: block;
	width: 30px;
	margin-left: 0px;
	top: -8px;
	
	cursor: pointer;
}

.darkmode .cb-enh-grid-size-selector-img path {
	fill: #002e43;
}

.darkmode .cb-enh-grid-size-selector-img path {
	fill: #68b4ef;
}

.cb-enh-grid-size-selector-img.cb-enh-first {
	margin-right: 25px;
}
/* end of styling for grid size selector */

/* styling for "more rooms" tab grid size selector */
.MoreRooms .list .roomCard a img {
	width: 100%;
	height: 100%;
}

.cb-enh-grid-size-selector-more-img {
	margin-top: -20px;
	margin-right: -10px !important;
	margin-left: 8px;
}
/* end of styling for "more rooms" tab grid size selector */

#cb-enh-add-multi-link {
	text-decoration: none;
}

.cb-enh-add-cam-icon {
	float: right;
	cursor: pointer;
}

.darkmode .cb-enh-add-cam-icon svg path, .darkmode #cb-enh-multi-tip-add-icon svg path {
	fill: #fbfbfb;
}

`;

addStyle(style);

let videoControlsContentHTML = `
<b class="ce-loc" data-ce-loc="vid_controls">Video Controls</b>:<br>

<div>
<input type="checkbox" checked id="cb-enh-video-controls-modal-show-logo"></input>
<label for="cb-enh-video-controls-modal-show-logo" class="ce-loc" data-ce-loc="show_site_logo">Show site logo</label><br>
</div>

<div>
<input type="checkbox" checked id="cb-enh-video-controls-use-intrf-def"></input>
<label for="cb-enh-video-controls-use-intrf-def" class="ce-loc" data-ce-loc="use_intrf_def">Use interactive fullscreen as default fullscreen mode</label><br>
</div>

<div>
<input type="checkbox" id="cb-enh-video-controls-modal-mirror-vid"></input>
<label for="cb-enh-video-controls-modal-mirror-vid" class="ce-loc" data-ce-loc="mirror_video">Mirror video</label><br>
</div>

<div>
<input type="checkbox" id="cb-enh-video-controls-modal-invert-vid"></input>
<label for="cb-enh-video-controls-modal-invert-vid" class="ce-loc" data-ce-loc="invert_video">Invert video</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-brightness" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-brightness" class="ce-loc" data-ce-loc="brightness">Brightness</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-contrast" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-contrast" class="ce-loc" data-ce-loc="contrast">Contrast</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-saturation" data-default="100" min="0" max="200" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-saturation" class="ce-loc" data-ce-loc="saturation">Saturation</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-sepia" data-default="0" value="0" min="0" max="100" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-sepia" class="ce-loc" data-ce-loc="sepia">Sepia</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-hue" data-default="0" value="0" min="0" max="360" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-hue" class="ce-loc" data-ce-loc="hue">Hue</label>
</div>

<div>
<input type="range" id="cb-enh-video-controls-modal-blur" data-default="0" value="0" min="0" max="100" class="cb-enh-vid-control-slider">
<label for="volume" for="cb-enh-video-controls-modal-blur" class="ce-loc" data-ce-loc="blur">Blur</label>
</div>

<div class="cb-enh-video-controls-modal-btns">
<input type="button" id="cb-enh-video-controls-modal-reset" value="Reset" class="ce-loc" data-ce-loc="reset">
</div>
`;

function getSiteLang() {
	return $('html').attr('lang');
}

let lang = getSiteLang();
if(lang !== 'en') {
	loadLocales(lang);
}

addStyle(style);

let regRedirPreventClick = false;
$(document).ready(function() {
	enableDarkMode();
	clearIntervalEx(intvWaitBody);

	if(getSetting('reg-redir') === 2) {
		setSetting('reg-redir', null);
		setSetting('reg-redir-room', null);
		
		$('.logo-zone a').click();
		regRedirPreventClick = true;
	}

	if(isMultiViewPage()) {
		initMultiView();
	}
	else if(window.location.pathname.startsWith('/roomlogin/')) {
		enhancePasswordedRoom();
	}
	else if('initialRoomDossier' in unsafeWindow) {
		enhanceRoom();
	}

	if(window.location.pathname.startsWith('/followed-cams/')) {
		enhanceFollowedList();
	}

	initAnimatedThumbs();
	initGridSizeSelector();
	initMultiViewUINavigation();
});

function loadLocales(lang) {
	xmlhttpRequest({
		method: 'GET',
		url: 'https://cb-enh.improper.dev/locale/' + lang + '.json',
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			gLocales = data['locales'];
		}
	});
}

function localizeStrings() {
	if(!gLocales) {
		return;
	}

	$('.ce-loc').each(function() {
		let v = $(this).data('ce-loc');
		if(gLocales[v]) {
			if($(this)[0].nodeName.toLowerCase() === 'input') {
				$(this).val(gLocales[v]);
			}
			else {
				$(this).text(gLocales[v]);
			}
		}
	});
}

function localizeStringsNextTick() {
	setTimeout(function() {
		localizeStrings();
	}, 1);
}

function localizeStringsFuture() {
	setTimeout(function() {
		localizeStrings();
	}, 50);
}

function getLocale(name, failsafe) {
	if(gLocales && gLocales[name]) {
		return gLocales[name];
	}

	if(failsafe) {
		return failsafe;
	}
}

function initAnimatedThumbs() {
	let $checkboxComponent = $("#animate_thumbnails_form .checkboxComponent");
	$('#animate_thumbnails_form label').removeAttr("style");
	$("#animate_thumbnails_form .disabledTooltipColor").remove();
	$checkboxComponent.removeClass("disabled");
	$checkboxComponent.css("cursor", "pointer");
	$("#animate_thumbnails_form input").removeAttr("disabled");
	$("#animate_thumbnails_form input").removeAttr("readonly");
	$("#id_animate_thumbnails").css("cursor", "inherit");

	let animateThumbnails = getSetting("animate_thumbnails");
	if(animateThumbnails === null) {
		animateThumbnails = true;
		setSetting("animate_thumbnails", animateThumbnails);
	}
	$checkboxComponent.toggleClass("checked", animateThumbnails);

	$(document).on("click", "#animate_thumbnails_form", function(e) {
		$checkboxComponent.toggleClass("checked");
		setSetting("animate_thumbnails", $checkboxComponent.hasClass("checked"));

		e.preventDefault();
		e.stopPropagation();
	});

	$(document).on('mouseenter', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
		e.preventDefault();
		e.stopImmediatePropagation();

		if(!getSetting("animate_thumbnails")) {
			return;
		}

		clearIntervalEx(currentHoverInterval);
		updateRoomThumb($(this));
		currentHoverInterval = setInterval(() => {
			updateRoomThumb($(this));
		}, 100);
	});

	$(document).on('mouseleave', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
		e.preventDefault();
		e.stopImmediatePropagation();
		clearIntervalEx(currentHoverInterval);
	});
}

function updateRoomThumb($el) {
	// Stop CB script from executing something on image load
	$el[0].onload = null;

	let uname = $el.parent().data('room');
	//$el.attr('src', 'https://roomimg.stream.highwebmedia.com/minifwap/' + uname + '.jpg?' + Math.random());

	let reqTime = Date.now();
	let req = new XMLHttpRequest();
	req.timeout = 2000;
	req.responseType = 'arraybuffer';
	req.addEventListener('load', function() {
		if(reqTime < lastLoadedThumbReqTime) {
			return;
		}

		lastLoadedThumbReqTime = reqTime;
		$el.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(req.response))));
	});

	req.open('GET', 'https://roomimg.stream.highwebmedia.com/minifwap/' + uname + '.jpg?' + Math.random());
	req.send();
}

document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';

document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';

function enhanceRoom(ajaxTransition=false) {
	unregisterMenuCommand(getLocale('get_vsurl', 'Get video source URL'));
	$('.cb-enh-row').remove();
	clearIntervalEx(gIntvCheckIfRoomIsOnline);

	if(!ajaxTransition) {
		let cFunc = function() {
			if(!gCurrentBroadcaster) {
				return;
			}

			xmlhttpRequest({
				method: 'GET',
				url: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/api/chatvideocontext/' + gCurrentBroadcaster + '/',
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
					'Referer': 'https://chaturbate.com/' + gCurrentBroadcaster + '/',
				},
				timeout: 60*1*1000,
				onload: function(resp) {
					let data;
					try {
						data = JSON.parse(resp.responseText);
					}
					catch(SyntaxError) {
						return;
					}

					if(!('hls_source' in data) || data['hls_source'] === '') {
						alert(getLocale('err_vurl', 'ERROR: No video URL.'));
						return;
					}

					setClipboard(data['hls_source'], 'text');
					alert(data['hls_source'] + '\n\n(' + getLocale('copied_to_clipboard', 'copied to clipboard') + ')');
				}
			});
		}
		registerMenuCommand(getLocale('get_vsurl', 'Get video source URL'), cFunc, 'g');

		if(getSetting('hide-vid-logo')) {
			addStyle('#VideoPanel .cbLogo { display: none; }');
		}

		clearIntervalEx(intvWaitVideo);
		intvWaitVideo = setInterval(function() {
			let $vid = getVideo();
			if($vid.length === 0) {
				return;
			}

			clearIntervalEx(intvWaitVideo);

			// Add Picture in Picture button to the player
			if(unsafeWindow.videoJsPlayer && typeof $vid[0].requestPictureInPicture !== 'undefined') {
				let PictureInPictureToggle = videojs.getComponent('pictureInPictureToggle');
				if(PictureInPictureToggle) {
					let pictureInPictureToggle = new PictureInPictureToggle(unsafeWindow.videoJsPlayer, {});
					unsafeWindow.videoJsPlayer.getChild('ControlBar').addChild(pictureInPictureToggle);
				}
			}

			// Add video resolution to the player
			$vid[0].addEventListener('resize', function(e) {
				$(".vjs-live-display").text('LIVE - ' + e.target.videoWidth + ' x ' + e.target.videoHeight);
			});
		});

		initSupportInfo();
	}

	if(unsafeWindow.initialRoomDossier === '') {
		// initialRoomDossier is set but is empty
		// room might be banned or blocked for user
		enhanceInaccessibleRoom();
		return;
	}

	let intv = setInterval(function() {
		if($('video.vjs-tech').length === 0) {
			return;
		}

		// Make clicking on live video feed don't pause it anymore
		$('video.vjs-tech').on('pause', function() {
			makeVideoPlay($('video.vjs-tech')[0]);
		});
		clearIntervalEx(intv);

		// Watch for AJAX page transition
		let currentUsername = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
		let pageTransitionIntv = setInterval(function() {
			let uname = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
			if(currentUsername != uname) {
				clearIntervalEx(pageTransitionIntv);
				enhanceRoom(true);
				currentUsername = uname;
			}
		}, 25);
	}, 25);

	let userData;
	let broadcasterName;
	if(!ajaxTransition) {
		userData = JSON.parse(unsafeWindow.initialRoomDossier);
		broadcasterName = userData.broadcaster_username;

		if(userData['room_status'] !== 'offline') {
			initBelowVideoButtons();
		}

		// Auto refresh page if offline room became online
		if(userData['room_status'] === 'offline') {
			gIntvCheckIfRoomIsOnline = setInterval(
			function() {
				$.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
					if(data['room_status'] !== 'offline') {
						clearIntervalEx(gIntvCheckIfRoomIsOnline);
						window.location.reload();
						return;
					}
				});
			}, 2000);
		}
	}
	else {
		broadcasterName = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
		stopRecording();
	}
	gCurrentBroadcaster = broadcasterName;

	let lang = getSiteLang();
	let intervalId = setInterval(() => {
		let $table = $('.BioContents > div > table');
		if($table.length === 0) {
			return;
		}
		clearIntervalEx(intervalId);

		let $offlineNotice = $('.offlineRoomNotice');
		if($offlineNotice.length > 0) {
			// Add offline avatar
			insertRoomAv($offlineNotice, broadcasterName);

			// Add last snapshot if possible
			fetchRoomSnapshot(broadcasterName, function(resp) {
				let $img = $('<img>');
				$img.addClass('cb-enh-offline-snapshot');
				$img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
				$offlineNotice.append($img);
			});
		}

		let $divSchedule = addBioRow('Schedule', false, '<div id="cb-enh-iframe"></div>');
		if(userData && userData.room_status === 'offline') {
			addBioRow('Last Subject', true, userData.room_title);
		}
		let $divRegion = addBioRow('Region', false, '<a href=""></a>');
		let $divOnlineFor = addBioRow('Online For', false);

		xmlhttpRequest({
			method: 'GET',
			url: 'https://camschedule.com/api/room/' + broadcasterName + '?lang=' + lang,
			timeout: 60*2*1000,
			onload: function(resp) {
				let data;
				try {
					data = JSON.parse(resp.responseText);
				}
				catch(SyntaxError) {
					return;
				}

				// Populate "region" row
				if(data['region'] !== '') {
					let href = '';
					if(data['region_id'] == 0) {
						href = '/asian-cams/';
					}
					else if(data['region_id'] == 1) {
						href = '/euro-russian-cams/';
					}
					else if(data['region_id'] == 2) {
						href = '/north-american-cams/';
					}
					else if(data['region_id'] == 3) {
						href = '/south-american-cams/';
					}
					else if(data['region_id'] == 4) {
						href = '/other-region-cams/';
					}

					let elA = $divRegion.children('.cb-enh-row-value').children('a')[0];
					elA.innerHTML = data['region'];
					elA.href = href;
					$divRegion.show();
				}

				// Populate "online for" row
				if(data['online_for'] && data['online_for'] !== '') {
					$divOnlineFor.children('.cb-enh-row-value')[0].innerHTML = data['online_for'];
					$divOnlineFor.show();
				}

				// Add schedule
				if(data['has_schedule']) {
					let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
					let iframeWrapper = document.getElementById('cb-enh-iframe');
					addElement(iframeWrapper, 'iframe', {
						src: 'https://camschedule.com/embed/schedule/' + broadcasterName + '?dark=' + darkMode + '&lang=' + lang,
						class: 'cb-enh-schedule-frame'
					});
					$divSchedule.show();
				}

				localizeStrings();
			}
		});

		// Add fanclub cost
		let $fanclubBtn = $('.fanclubButton');
		if($fanclubBtn.length > 0) {
			$.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
				if(!data['performer_has_fanclub'] || data['fan_club_cost'] === null || $fanclubBtn.length === 0) {
					return;
				}

				let $a = $fanclubBtn.find('span a');
				if($a.length === 0) {
					return;
				}

				$a.text( $a.text() + ' (' + data['fan_club_cost'] + '/m)' );
			});
		}

		initMultiViewUIRoom();
	}, 500);

	// Insert model avatar on private etc. video board
	clearIntervalEx(intvUpdateAvatarInPrivBoard);
	intvUpdateAvatarInPrivBoard = setInterval(() => {
		let $el = $('#VideoPanel div[ts]').eq(0);
		if($el.data('cb-enh-av')) {
			return;
		}

		let $div = $el.find('div:first-child').eq(0);
		if($div.length === 0) {
			return;
		}

		$el.data('cb-enh-av', true);
		let $avDiv = insertRoomAv($div, broadcasterName);
		$avDiv.css('margin', '0 auto');
		$avDiv.css('margin-bottom', '10px');
	}, 500);
}

function initBelowVideoButtons() {
	// Implement live video stream controls
	// Add video controls button
	let intvAddVideoControlsBtn = setInterval(() => {
		// Wait until interface loads
		if($('#satisfactionScore').length === 0) {
			return;
		}
		clearIntervalEx(intvAddVideoControlsBtn);

		let $videoControlsModal = $('<div id="cb-enh-video-controls-modal" class="whiteModal cb-enh-tab-bar-modal noselect"><div class="cb-enh-tab-bar-modal-arrow-down"></div><span id="cb-enh-video-controls-modal-content"></span></div>');
		$(".tabBar").prepend($videoControlsModal);
		$("#cb-enh-video-controls-modal-content").html(videoControlsContentHTML);
		localizeStringsNextTick();

		// Also add screenshot button
		$('<div id="cb-enh-video-controls-screenshot" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="screenshot">Screenshot</div>').insertAfter("#satisfactionScore");

		// Also add recording button
		$('<div id="cb-enh-video-controls-record" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="start_rec">Start recording</div>').insertAfter("#satisfactionScore");

		// Add video controls button
		$('<div id="cb-enh-video-controls-btn" class="cb-enh-video-bar-btn noselect"><span class="ce-loc" data-ce-loc="vid_controls">Video Controls</div>').insertAfter("#satisfactionScore");
	}, 100);

	$(document).on("click", "#cb-enh-video-controls-btn", function(e) {
		e.preventDefault();
		e.stopPropagation();

		if(gVideoControlsModalShown) {
			setVideoControlsVisible(false);
			return;
		}

		setVideoControlsVisible(true);

		$("#cb-enh-video-controls-modal-show-logo")[0].checked = !getSetting('hide-vid-logo');
		$("#cb-enh-video-controls-use-intrf-def")[0].checked = getSetting('use-intrf-def');

		let $btn = $(this);
		let $modal = $("#cb-enh-video-controls-modal");

		let off = $btn.offset();
		off.top -= $btn.outerHeight() + $modal.outerHeight();
		off.left -= $($modal).outerWidth() / 2;
		$modal.offset(off);

		let height = $modal.outerHeight();
		let $arrow = $("#cb-enh-video-controls-modal .cb-enh-tab-bar-modal-arrow-down");
		$arrow.offset({
			left: $btn.offset().left + $btn.outerWidth() / 2 - $arrow.outerWidth() / 2,
			top: off.top + height
		});
	});

	$(window).click(function(e) {
		if(gVideoControlsModalShown && $(e.target).closest('#cb-enh-video-controls-modal').length === 0) {
			setVideoControlsVisible(false);
		}
	});

	$(document).on("click", "#cb-enh-video-controls-modal-show-logo", function() {
		let show = $(this)[0].checked;
		$("#VideoPanel .cbLogo").toggle(show);
		setSetting('hide-vid-logo', !show);
	});

	$(document).on("click", "#cb-enh-video-controls-use-intrf-def", function() {
		let enabled = $(this)[0].checked;
		setSetting('use-intrf-def', enabled);
	});

	$(document).on("click", "#cb-enh-video-controls-modal-mirror-vid", function() {
		$(".videoPlayerDiv video").toggleClass("cb-enh-video-mirrored", $(this)[0].checked);
	});

	$(document).on("click", "#cb-enh-video-controls-modal-invert-vid", function() {
		$(".videoPlayerDiv video").toggleClass("cb-enh-video-inverted", $(this)[0].checked);
	});

	$(document).on("input", "#cb-enh-video-controls-modal-brightness", function() {
		vidFilters[0] = "brightness(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-contrast", function() {
		vidFilters[1] = "contrast(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-saturation", function() {
		vidFilters[2] = "saturate(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-sepia", function() {
		vidFilters[3] = "sepia(" + $(this).val() + "%)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-hue", function() {
		vidFilters[4] = "hue-rotate(" + $(this).val() + "deg)";
		updateVideoFilters();
	});

	$(document).on("input", "#cb-enh-video-controls-modal-blur", function() {
		vidFilters[5] = "blur(" + $(this).val() + "px)";
		updateVideoFilters();
	});

	$(document).on("click", "#cb-enh-video-controls-modal-reset", function() {
		vidFilters = [];
		updateVideoFilters();

		$(".videoPlayerDiv video").removeClass("cb-enh-video-mirrored");
		$(".videoPlayerDiv video").removeClass("cb-enh-video-inverted");

		$("#cb-enh-video-controls-modal-mirror-vid")[0].checked = false;
		$("#cb-enh-video-controls-modal-invert-vid")[0].checked = false;

		$(".cb-enh-vid-control-slider").each(function() {
			$(this).val($(this).attr("data-default"));
		});
	});

	// Screenshot button
	$(document).on("click", "#cb-enh-video-controls-screenshot", function(e) {
		e.preventDefault();
		e.stopPropagation();
		captureScreenshot();
	});

	// Record button
	$(document).on("click", "#cb-enh-video-controls-record", function(e) {
		e.preventDefault();
		e.stopPropagation();

		if(!gRecording) {
			startRecording();
		}
		else {
			stopRecording();
		}
	});

	// Fix for chat window not closing when mouse leaves it
	let insideChat = false;
	document.addEventListener('mouseover',
		function(e) {
			if($('.draggableCanvasChatWindow').find(e.target).length > 0) {
				insideChat = true;
			}
		}
	);

	document.addEventListener('mouseout',
		function(e) {
			let $input = $('.fullvideoInputFieldChat');
			if(insideChat && $input.length > 0 && document.activeElement !== $input[0] && $('.draggableCanvasChatWindow > div[ts="n"]').find(e.target).length > 0) {
				insideChat = false;
				resetTheaterModeChatVisibilityState();
			}
		}
	);

	document.addEventListener('click',
		function(e) {
			let $chatBtn = $('#TheaterModePlayer #chat-btn');
			if($chatBtn.length > 0 && e.target === $chatBtn[0]) {
				resetTheaterModeChatVisibilityState();
			}
		}
	, true);
}

function resetTheaterModeChatVisibilityState() {
	setTimeout(
		function() {
			if($('#TheaterModeRoomContents').length > 0) {
				$('#TheaterModeRoomContents')[0].click();
			}
		},
		1
	);
}

function setVideoControlsVisible(visible) {
	$("#cb-enh-video-controls-modal").toggle(visible);
	gVideoControlsModalShown = visible;
	localizeStringsNextTick()
}

// Support info - start
function initSupportInfo() {
	$(document).on('click', '.cb-enh-acc-info-inner', function() {
		loutrreg(gCurrentBroadcaster);
	});

	let intvWaitBuyBox = setInterval(() => {
		let $bBox = $('div[data-paction=CurrentShowBuyBox]').parent();
		if($bBox.length === 0) {
			return;
		}

		clearIntervalEx(intvWaitBuyBox);
		if($('#cb-enh-acc-info').length > 0) {
			return;
		}

		let userType = getUserType();
		let msg = getSupportMessage(userType);

		if(msg === null) {
			return;
		}

		let bBoxHtml = `
<div id="cb-enh-acc-info" class="noselect">
	<div class="cb-enh-acc-info-outer">
		<div class="cb-enh-acc-info-inner" data-utype="${userType}">
			${msg}
		</div>
	</div>
</div>
		`;
		$bBox.append(bBoxHtml);
	}, 50);

	let intvUpdateAccInfoSize = setInterval(function() {
		if($('#VideoPanel').length === 0) {
			return;
		}

		let $accInfo = $('#cb-enh-acc-info');
		if($accInfo.length === 0) {
			return;
		}

		if($('#VideoPanel').innerWidth() >= 1000) {
			$accInfo.css('font-size', '15px');
			$accInfo.css('width', '500px');
			$accInfo.show();
			return;
		}

		let $parent = $accInfo.parent();
		let w1 = $parent.innerWidth();
		let chw1 = $parent.children().eq(0).innerWidth();
		let chw2 = $parent.children().eq(1).innerWidth();
		let w2 = w1 - chw1 - chw2;

		if(w2 <= 30) {
			$accInfo.hide();
			return;
		}

		$accInfo.css('width', (w2 - 10) + 'px');
		$accInfo.css('font-size', '12px');
		$accInfo.show();
	}, 200);
}

function isLogged() {
	return $('.user_information_header_username').length !== 0;
}

function getUserType() {
	let userType = 0;
	if(isLogged()) {
		userType = 2;
		let gaq = $("#gaq").html();
		let p = atob(acre1 + acre2);
		let re = new RegExp(p);
		let m = gaq.match(re);
		if(m) {
			let afn = m[1];
			if(afn !== atob(rev('gYtZWMwN3N'))) {
				userType = 1;
			}
		}
	}

	return userType;
}

function getSupportMessage(userType) {
	if(userType === 0) {
		return '<b>Chaturbate Enhancer <span class="ce-loc" data-ce-loc="msg">message</msg></b>: <span class="ce-loc" data-ce-loc="please_support">Please support</span> Chaturbate Enhancer <span class="ce-loc" data-ce-loc="ac_msg_dev">development by</span> <u class="ce-loc" data-ce-loc="ac_msg_0">creating free Chaturbate account</u>. <span class="ce-loc" data-ce-loc="thanks">Thank you</span> ❤️.';
	}
	else if(userType === 1) {
		return '<b>Chaturbate Enhancer <span class="ce-loc" data-ce-loc="msg">message</span></b>: <span class="ce-loc" data-ce-loc="please_support">Please support</span> Chaturbate Enhancer <span class="ce-loc" data-ce-loc="ac_msg_dev">development by</span> <u class="ce-loc" data-ce-loc="ac_msg_1">creating new Chaturbate account</u>. <span class="ce-loc" data-ce-loc="thanks">Thank you</span> ❤️.';
	}
	
	return null;
}

function loutrreg() {
	setSetting('reg-redir', 1);
	setSetting('reg-redir-room', gCurrentBroadcaster);

	if(isLogged()) {
		$('a[href="/auth/logout/"]').click();
		$(".modalAlert .dialog .accept").click();
	}
	else {
		regRedir(gCurrentBroadcaster);
	}
}

function regRedir(room) {
	let rurl = '/accounts/register/';
	if(room) {
		// rurl += '?room=' + room;
		rurl = '/' + room + '/?join_overlay=1&disable_sound=1';
	}
	setSetting('reg-redir', 2);
	setSetting('reg-redir-room', room);
	window.location.href = rurl;
}
// Support info - end

function getVideo() {
	return $(".videoPlayerDiv video");
}

function isVideoPlaying(vid) {
	return !vid.paused && !vid.ended && vid.readyState > 2;
}

function playIgnoreErrors(vid) {
	try {
		vid.play();
	} catch(err) {};
}

function makeVideoPlay(vid) {
	playIgnoreErrors(vid);
	setTimeout(function() { playIgnoreErrors(vid) }, 1);
	setTimeout(function() { playIgnoreErrors(vid) }, 10);
	setTimeout(function() { playIgnoreErrors(vid) }, 25);
	setTimeout(function() { playIgnoreErrors(vid) }, 50);
	setTimeout(function() { playIgnoreErrors(vid) }, 100);
}

function setSetting(name, value) {
	gSettings[name] = value;
	localStorage.setItem('cb-enh-settings', JSON.stringify(gSettings));
}

function getSetting(name, value) {
	if(typeof gSettings[name] !== 'undefined') {
		return gSettings[name];
	}
	return null;
}

function loadSettings() {
	let settings = localStorage.getItem('cb-enh-settings');
	if(settings === null) {
		settings = {};
	}
	else {
		settings = JSON.parse(settings);
	}

	gSettings = settings;
}

let vidFilters = [];
function updateVideoFilters() {
	getVideo().css("filter", vidFilters.join(" "));
}

let acre1 = 'Z2FcKCdzZXQnLCAnZGltZW5zaW';

function enhanceInaccessibleRoom() {
	let lang = getSiteLang();

	// Display video of inaccessible room
	let $baseRoomContentDiv =  $("div.BaseRoomContents div")
	if($baseRoomContentDiv.length === 0) {
		return;
	}

	$baseRoomContentDiv = $baseRoomContentDiv.eq(0);
	if($baseRoomContentDiv.text().indexOf("Access denied") !== 0) {
		return;
	}

	$baseRoomContentDiv.append('<br><span class="ce-loc" data-ce-loc="try_load">Chaturbate Enhancer will try to load video and bio of this room.</span><br><br>');

	let $langForm = $("form[action='/set_language/'] input[name='next']");
	if($langForm.length === 0) {
		return;
	}
	let username = $("form[action='/set_language/'] input[name='next']")[0].value.slice(1, -1);
	gCurrentBroadcaster = username;

	addStyle(`
		.BaseRoomContents div {
			font-size: 14px !important;
			font-family: UbuntuMedium, Arial, Helvetica, sans-serif;
			font-weight: normal;
		}

		.darkmode .BaseRoomContents {
			border-color: transparent !important;
			background-color: #202c39 !important;
		}

		.ce-row-1 {
			color: #0a5a83;
		}

		.darkmode .ce-row-1 {
			color: white;
		}
	`);

	let $upperHolder = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder);

	let $videoHolder = $('<div></div>');
	$baseRoomContentDiv.append($videoHolder);

	let $upperHolder2 = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder2);

	let $upperHolder3 = $('<div></div>');
	$baseRoomContentDiv.append($upperHolder3);

	let $infoHolder = $('<div></div>');
	$baseRoomContentDiv.append($infoHolder);

	let $infoHolder2 = $('<div></div>');
	$baseRoomContentDiv.append($infoHolder2);

	let $scheduleHolder = $('<div></div>');
	$baseRoomContentDiv.append($scheduleHolder);

	let isOnline = false;

	// video type
	xmlhttpRequest({
		method: 'GET',
		url: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/api/chatvideocontext/' + username + '/',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
			'Referer': 'https://chaturbate.com/' + username + '/',
		},
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			if($baseRoomContentDiv.length === 0) {
				return;
			}

			let playVideo = true;
			if(data['room_status'] !== 'public') {
				let $avDiv = $("<div></div>");
				$upperHolder.append($avDiv);
				insertRoomAv($avDiv, username);
				$upperHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="room_status">Room status is</span>: <span class="ce-loc" data-ce-loc="status_' + data['room_status'] + '">' + data['room_status'] + '</span><br>');
				playVideo = false;
			}
			else {
				isOnline = true;
				$("#cb-enh-inac-load-chat").show()
			}

			if(data['hls_source'] === '') {
				playVideo = false;
			}

			if(data['room_title']) {
				let $span;
				if(data['room_status'] !== 'offline') {
					$span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="subject">Subject</span>: <span></span></span>');
				}
				else {
					$span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="last_subject">Last Subject</span>: <span></span></span>');
				}
				$span.find('span').eq(1).text(data['room_title']);
				$upperHolder.append($span);
				$upperHolder.append('<br><br>');
			}

			if(playVideo) {
				let $video = $('<video controls autoplay muted data-listener-count-webkitendfullscreen="1" class="vjs-tech cb-enh-video" id="vjs_video_3_html5_api" tabindex="-1" role="application" poster="https://cbjpeg.stream.highwebmedia.com/stream?room=' + username + '&f=' + Math.random() + '"></video>');
				$videoHolder.append($video);

				$video.on('pause', function() {
					makeVideoPlay($video[0]);
				});

				$video.on('click', function() {
					makeVideoPlay($video[0]);
				});

				let hls = new Hls();
				hls.loadSource(data['hls_source']);
				hls.attachMedia($video[0]);
			}

			if(data['age']) {
				$upperHolder3.append('<br><br><span class="ce-loc ce-row-1" data-ce-loc="age">Age</span>: ' + data['age'] + '<br>');
			}

			if(data['broadcaster_gender']) {
				$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="gender">Gender</span>: <span class="ce-loc" data-ce-loc="gender_' + data['broadcaster_gender'][0] + '">'  + data['broadcaster_gender'] + '</span><br>');
			}

			if(data['num_viewers']) {
				if(data['room_status'] !== 'offline') {
					$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="viewers">Viewers</span>: ' + data['num_viewers'] + '<br>');
				}
				else {
					$upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="last_viewers">Last Viewers</span>: ' + data['num_viewers'] + '<br>');
				}
			}

			if(data['performer_has_fanclub']) {
				$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="has_fanclub">Has Fanclub</span>: <span class="ce-loc" data-ce-loc="yes">Yes</span><br>');
			}
			else {
				$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="has_fanclub">Has Fanclub</span>: <span class="ce-loc" data-ce-loc="no">No</span><br>');
			}

			if('satisfaction_score' in data) {
				let sc = data['satisfaction_score'];
				if('percent' in sc && 'up_votes' in sc && 'down_votes' in sc) {
					$infoHolder2.append('<span class="ce-loc ce-row-1" data-ce-loc="satisfaction_score">Satisfaction Score</span>: ' + sc['percent'] + '% (' + sc['up_votes'] + ' <span class="ce-loc" data-ce-loc="up">up</span>, ' + sc['down_votes'] + ' <span class="ce-loc" data-ce-loc="down">down</span>)<br>');
				}
			}

			localizeStrings();
		},
		onerror: function() {
			$upperHolder.append('<br>' + getLocale('err_vid', 'ERROR: Unable to load video.'));
		}
	});

	// Fetch info about room
	xmlhttpRequest({
		method: 'GET',
		url: 'https://camschedule.com/api/room/' + username + '?lang=' + lang,
		timeout: 60*2*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				return;
			}

			let $loadChatHref = $('<a id="cb-enh-inac-load-chat" style="display:none;" class="ce-loc" data-ce-loc="try_load_chat">Click here to try to load chat.</a><br>');
			$upperHolder2.append($loadChatHref);
			if(isOnline) {
				$loadChatHref.show();
			}

			$loadChatHref.on('click', function(e) {
				$loadChatHref.hide();

				e.preventDefault();
				e.stopPropagation();

				$videoHolder.empty();
				addElement($videoHolder[0], 'iframe', {
					src: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/embed/' + username + '/',
					class: 'cb-enh-chat-frame'
				});
			});

			// Populate "region" row
			if(data['region'] !== '') {
				let href = '';
				if(data['region_id'] == 0) {
					href = '/asian-cams/';
				}
				else if(data['region_id'] == 1) {
					href = '/euro-russian-cams/';
				}
				else if(data['region_id'] == 2) {
					href = '/north-american-cams/';
				}
				else if(data['region_id'] == 3) {
					href = '/south-american-cams/';
				}
				else if(data['region_id'] == 4) {
					href = '/other-region-cams/';
				}
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="region">Region</span>: <a href="' + href + '">' + data['region'] + '</a><br>');
			}

			// Populate "online for" row
			if(data['online_for'] && data['online_for'] !== '') {
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="online_for">Online For</span>: ' + data['online_for'] + '<br>');
			}
			else if(data['last_online_f'] && data['last_online_f'] !== '') {
				$infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="last_online">Last Online</span>: ' + data['last_online_f'] + '<br>');
			}

			let info = {
				'real_name': 'Real Name',
				'birthday': 'Birthday',
				'followers_f': 'Followers',
				'location': 'Location',
				'languages': 'Languages',
				'smoke_drink': 'Smoke / Drink',
				'body_type': 'Body Type',
				'body_decorations': 'Body Decorations',
			};

			Object.keys(info).forEach(function(k) {
				let v = info[k];
				if(data[k]) {
					let $span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="' + k + '">' + v + '</span>: <span></span></span>');
					$span.find('span').eq(1).text(data[k]);
					$infoHolder.append($span);
					$infoHolder.append('<br>');
				}
			});

			// Add schedule
			if(data['has_schedule']) {
				$scheduleHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="schedule">Schedule</span>: <br>');
				let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
				addElement($scheduleHolder[0], 'iframe', {
					src: 'https://camschedule.com/embed/schedule/' + username + '?dark=' + darkMode + '&lang=' + lang,
					class: 'cb-enh-schedule-frame'
				});
			}

			localizeStrings();
		}
	});
}

function enhancePasswordedRoom() {
	// @todo
}

function addBioRow(name, visible = true, value = '') {
	let loc = name.replace(' ', '_').toLowerCase();
	let $el = $('<tr class="cb-enh-row" style="' + (visible ? '' : 'display: none; ') + 'font-size: 14px; font-weight: normal; line-height: 15px; vertical-align: top; text-align: left;"><td class="label" style="padding-bottom: 9px; font-family: UbuntuMedium, Arial, Helvetica, sans-serif; height: 16px;"><span><span class="ce-loc" data-ce-loc="' + loc + '">' + name + '</span>:</span></td><td class="contentText cb-enh-row-value" style="font-size: 14px; line-height: 16px; font-family: UbuntuRegular, Arial, Helvetica, sans-serif;">' + value + '</td></tr>');

	let $psContainers = $('.BioContents > div > table > .psContainer');
	let $smContainers = $('.BioContents > div > table > .smContainer');

	if($psContainers.length > 0) {
		$psContainers.last().after($el);
	}
	else if($smContainers.length > 0) {
		$smContainers.last().after($el);
	}
	else {
		$('.BioContents > div > table > tr').slice(-2).first().after($el);
	}

	return $el;
}

let acre2 = '9uNCcsICcoW2EtekEtWjAtOS1dKyknXCk7';

function insertRoomAv($div, username) {
	let $avDiv = $('<div class="cb-enh-avatar"></div>');
	$div.prepend($avDiv);
	addElement($avDiv[0], 'img', {
		src: 'https://camschedule.com/assets/img/avatar.png',
		alt: '',
		onload: 'this.style.opacity=1'
	});

	addElement($avDiv[0], 'img', {
		src: 'https://cb-enh-thumb.improper.dev/av/' + username + '.jpg',
		alt: '',
		onload: 'this.style.opacity=1'
	});
	return $avDiv;
}

document.addEventListener("keydown",
function(e) {
	if(`${e.code}` === 'KeyX' && e.ctrlKey) {
		if(document.activeElement) {
			if(document.activeElement.type === 'input' || document.activeElement.type === 'textarea' || document.activeElement.hasAttribute('contenteditable')) {
				return;
			}
		}

		captureScreenshot();
	}
}
);

// Screenshoting start
function captureScreenshot() {
	if(gCapturingScreenshot) {
		return;
	}

	let $vid = getVideo();
	if($vid.length === 0) {
		return;
	}

	gCapturingScreenshot = true;
	let canvas = captureVideoFrame($vid[0]);
	gCapturingScreenshot = false;
	if(!canvas) {
		alert(getLocale('err_ss', 'ERROR: Failed to capture screenshot!'));
		return;
	}

	let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
	let link = document.createElement('a');
	let date = new Date();
	link.download = getFileName(username, '.png', date);
	link.href = canvas.toDataURL();
	link.click();
}

function captureVideoFrame(video) {
	let canvas = document.createElement("canvas");
	canvas.width = video.videoWidth;
	canvas.height = video.videoHeight;
	canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
	return canvas;
}
// Screenshoting end

// Recording start
function startRecording() {
	if(gRecording) {
		return;
	}

	let $vid = getVideo();
	if($vid.length === 0) {
		alert(getLocale('err_no_rec_video', 'ERROR: There is no video to record!'));
		return;
	}

	$("#cb-enh-video-controls-record").find("span").text(getLocale("stop_rec", "Stop recording"));
	$("#cb-enh-video-controls-record").find("span").data("ce-loc", "stop_rec");
	$("#cb-enh-video-controls-record").addClass("cb-enh-active");
	gRecording = true;
	gRecordingCanceledByUser = false;

	let lengthInMS = 1000*60*10;
	let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
	console.log('Starting recording', username);

	let stream = captureStream($vid[0]);
	if(stream === 'err-no-func') {
		gRecording = false;
		alert(getLocale('err_no_rec_support', 'ERROR: Your browser does not seem to support recording. Please install latest version of modern browser. If you think that\'s mistake, please fill an issue report. Thank you!'));
		return;
	}

	$vid[0].play();

	if($vid[0].muted) {
		stream.getAudioTracks().forEach( t => stream.removeTrack( t ) );
	}

	let date = new Date();
	startRecordingStream(stream, lengthInMS).then((recordedChunks) => {
		let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
		console.log(
			`Successfully recorded ${recordedBlob.size} bytes of ${recordedBlob.type} media.`
		);

		let link = document.createElement('a');
		link.download = getFileName(username, '.webm', date);
		link.href = URL.createObjectURL(recordedBlob);
		link.click();

		gRecordingStream = null;
		gRecording = false;

		if(!gRecordingCanceledByUser) {
			// Start next recording
			startRecording();
			return;
		}

		gRecordingCanceledByUser = false;

		// UI
		$("#cb-enh-video-controls-record").find("span").text(getLocale("start_rec", "Start recording"));
		$("#cb-enh-video-controls-record").find("span").data("ce-loc", "start_rec");
		$("#cb-enh-video-controls-record").removeClass("cb-enh-active");
	});
}

function wait(delayInMS) {
	return new Promise((resolve) => setTimeout(resolve, delayInMS));
}

function startRecordingStream(stream, lengthInMS) {
	let options = {
		mimeType: 'video/webm'
	};

	let recorder = new MediaRecorder(stream, options);
	let data = [];

	recorder.ondataavailable = (event) => { data.push(event.data); }
	recorder.start(250);
	gRecordingStream = stream;

	let stopped = new Promise((resolve, reject) => {
		recorder.onstop = (event) => { resolve(event.name); }
		recorder.onerror = (event) => { reject(event.name); }
	});

	let recorded = wait(lengthInMS).then(() => {
		if(recorder.state === "recording") {
			recorder.stop();
		}
	});

	return Promise.any([stopped, recorded]).then(() => data);
}

function stopRecordingStream(stream) {
	stream.getTracks().forEach((track) => track.stop());
}

function stopRecording() {
	if(!gRecording || !gRecordingStream) {
		return;
	}

	gRecordingCanceledByUser = true;
	stopRecordingStream(gRecordingStream);
}
// Recording end

// enhanceFollowedList
function updateRoomListStatuses() {
	$('.room_list_room').each(function() {
		// Room name
		let room = $(this).find('a').eq(0).data('room');

		let _this = this;
		$.getJSON('https://chaturbate.com/api/biocontext/' + room + '/', function(data) {
			if(data['room_status'] === 'public') {
				let $label = $(_this).find('.thumbnail_label');
				if($label.length === 0) {
					$label = $(_this).find('.thumbnail_label_featured');
				}

				if(!$label.hasClass('thumbnail_label_c_new') && !$label.hasClass('thumbnail_label_c_gaming')) {
					$label.remove();
				}
				return;
			}

			$(_this).find('.thumbnail_label').remove();
			$(_this).find('.thumbnail_label_featured').remove();

			let $label = $('<div class="thumbnail_label ce-loc"></div>');
			$label.text( getLocale('status_' + data['room_status'], data['room_status']) );

			if(data['room_status'] === 'private' || data['room_status'] === 'group' || data['room_status'] === 'hidden') {
				$label.addClass('thumbnail_label_c_private_show');
			}
			else if(data['room_status'] === 'offline') {
				$label.addClass('thumbnail_label_offline');
			}
			else {
				$label.addClass('thumbnail_label_c');
			}
			$(_this).append($label);
		});
	});
}

function enhanceFollowedList() {
	clearIntervalEx(intvUpdateFollowedList);
	updateRoomListStatuses();
	intvUpdateFollowedList = setInterval(function() {
		updateRoomListStatuses();
	}, 1000 * 30);
}
//

function setGridSize(width) {
	$('#room_list').css('grid-template-columns', 'repeat(auto-fill,minmax(' + width + 'px,max-content))');
}

// Set grid style by injecting new CSS style (so it can be done before page render)
function setGridSize2(width) {
	let gridStyle = `
	#room_list {
		grid-template-columns: repeat(auto-fill,minmax(` + width + `px,max-content));
	}
	`;
	addStyle(gridStyle);
}

function setMoreRoomsGridSize(width) {
	let gridStyle = `
	.MoreRooms .list .roomCard {
		width: ` + width + `px;
	}
	`;
	addStyle(gridStyle);
}

// grid-big.svg
let gGridIconSvgBig = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M140.001 543.307V236.001h307.306v307.306H140.001Zm0 372.692V608.693h307.306v307.306H140.001Zm372.692-372.692V236.001h307.306v307.306H512.693Zm0 372.692V608.693h307.306v307.306H512.693ZM185.385 497.924h216.539V281.385H185.385v216.539Zm372.691 0h216.539V281.385H558.076v216.539Zm0 372.691h216.539V654.076H558.076v216.539Zm-372.691 0h216.539V654.076H185.385v216.539Zm372.691-372.691Zm0 156.152Zm-156.152 0Zm0-156.152Z"/></svg>';

// grid-medium.svg
let gGridIconSvgMedium = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M197.694 915.999q-23.529 0-40.611-17.082-17.082-17.082-17.082-40.611V293.694q0-23.529 17.082-40.611 17.082-17.082 40.611-17.082h564.612q23.529 0 40.611 17.082 17.082 17.082 17.082 40.611v564.612q0 23.529-17.082 40.611-17.082 17.082-40.611 17.082H197.694Zm0-45.384h153.845V704.461H185.385v153.845q0 5.385 3.462 8.847 3.462 3.462 8.847 3.462Zm199.229 0h166.154V704.461H396.923v166.154Zm211.538 0h153.845q5.385 0 8.847-3.462 3.462-3.462 3.462-8.847V704.461H608.461v166.154ZM185.385 659.077h166.154V492.923H185.385v166.154Zm211.538 0h166.154V492.923H396.923v166.154Zm211.538 0h166.154V492.923H608.461v166.154ZM185.385 447.539h166.154V281.385H197.694q-5.385 0-8.847 3.462-3.462 3.462-3.462 8.847v153.845Zm211.538 0h166.154V281.385H396.923v166.154Zm211.538 0h166.154V293.694q0-5.385-3.462-8.847-3.462-3.462-8.847-3.462H608.461v166.154Z"/></svg>';

// grid-small.svg
let gGridIconSvgSmall = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M99.232 869.076V282.924h761.536v586.152H99.232Zm45.384-405.691h133.847V328.308H144.616v135.077Zm179.23 0h133.462V328.308H323.846v135.077Zm178.846 0h133.847V328.308H502.692v135.077Zm179.23 0h133.462V328.308H681.922v135.077Zm0 180.461h133.462V508.769H681.922v135.077Zm-179.23 0h133.847V508.769H502.692v135.077Zm-178.846 0h133.462V508.769H323.846v135.077Zm-45.383-135.077H144.616v135.077h133.847V508.769Zm403.459 314.923h133.462V689.23H681.922v134.462Zm-179.23 0h133.847V689.23H502.692v134.462Zm-178.846 0h133.462V689.23H323.846v134.462Zm-179.23 0h133.847V689.23H144.616v134.462Z"/></svg>';

// grid-small-x.svg
let gGridIconSvgSmallX = '<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48"><path d="M180 876h105V771H180v105Zm165 0h105V771H345v105Zm165 0h105V771H510v105Zm165 0h105V771H675v105ZM180 381h105V276H180v105Zm0 165h105V441H180v105Zm0 165h105V606H180v105Zm165-330h105V276H345v105Zm0 165h105V441H345v105Zm0 165h105V606H345v105Zm165-330h105V276H510v105Zm0 165h105V441H510v105Zm0 165h105V606H510v105Zm165-330h105V276H675v105Zm0 165h105V441H675v105Zm0 165h105V606H675v105ZM180 936q-24 0-42-18t-18-42V276q0-24 18-42t42-18h600q24 0 42 18t18 42v600q0 24-18 42t-42 18H180Z"/></svg>';

function initGridSizeSelector() {
	if(gIsInMultiView) {
		return;
	}

	// Add grid size selector buttons
	let $topSelection = $('.top-section');
	if($topSelection.length === 0) {
		// return;
	}

	let $svg1 = $(gGridIconSvgBig);
	$svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
	$topSelection.append($svg1);
	$svg1.on('click', function() {
		setGridSize(280);
		setSetting('grid-size', 280);
	});

	let $svg2 = $(gGridIconSvgMedium);
	$svg2.addClass('cb-enh-grid-size-selector-img');
	$svg2.on('click', function() {
		setGridSize(250);
		setSetting('grid-size', 250);
	});
	$topSelection.append($svg2);

	let $svg3 = $(gGridIconSvgSmall);
	$svg3.addClass('cb-enh-grid-size-selector-img');
	$topSelection.append($svg3);
	$svg3.on('click', function() {
		setGridSize(220);
		setSetting('grid-size', 220);
	});

	let $svg4 = $(gGridIconSvgSmallX);
	$svg4.addClass('cb-enh-grid-size-selector-img');
	$topSelection.append($svg4);
	$svg4.on('click', function() {
		setGridSize(180);
		setSetting('grid-size', 180);
	});
	
	// Init "more rooms" grid size selector buttons
	let intvWaitMoreRooms = setInterval(function() {
		let $moreRooms = $('.MoreRooms');
		if($moreRooms.length === 0) {
			return;
		}

		clearIntervalEx(intvWaitMoreRooms);
		let $msvg4 = $(gGridIconSvgSmallX);
		$msvg4.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg4);
		$msvg4.on('click', function() {
			setMoreRoomsGridSize(180);
			setSetting('grid-more-size', 180);
		});

		let $msvg3 = $(gGridIconSvgSmall);
		$msvg3.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg3);
		$msvg3.on('click', function() {
			setMoreRoomsGridSize(210);
			setSetting('grid-more-size', 210);
		});

		let $msvg2 = $(gGridIconSvgMedium);
		$msvg2.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
		$moreRooms.prepend($msvg2);
		$msvg2.on('click', function() {
			setMoreRoomsGridSize(260);
			setSetting('grid-more-size', 260);
		});

		let $msvg1 = $(gGridIconSvgBig);
		$msvg1.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img cb-enh-first');
		$moreRooms.prepend($msvg1);
		$msvg1.on('click', function() {
			setMoreRoomsGridSize(300);
			setSetting('grid-more-size', 300);
		});
	});
}

// MULTI VIEW START
let multiLinkHTML = `
<li style="display: block;">
<a href="/#multicam" target="_blank" rel="noopener" style="color: rgb(255, 255, 255); font: 400 13.999px ubuntumedium, Arial, Helvetica, sans-serif;">
MULTI CAM VIEWER
</a>
</li>
`;

let multiAddSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 96 960 960" width="20"><path d="M516 661.999h51.999v-132h132v-51.998h-132v-132H516v132H384v51.998h132v132Zm-201.692 134q-27.008 0-45.657-18.65-18.65-18.65-18.65-45.658V276.309q0-27.008 18.65-45.658 18.649-18.65 45.657-18.65h455.383q27.007 0 45.657 18.65 18.65 18.65 18.65 45.658v455.382q0 27.008-18.65 45.658-18.65 18.65-45.657 18.65H314.308Zm0-51.999h455.383q4.615 0 8.462-3.846 3.846-3.847 3.846-8.463V276.309q0-4.616-3.846-8.463-3.847-3.846-8.462-3.846H314.308q-4.616 0-8.462 3.846-3.847 3.847-3.847 8.463v455.382q0 4.616 3.847 8.463 3.846 3.846 8.462 3.846ZM190.309 919.997q-27.007 0-45.657-18.65-18.65-18.65-18.65-45.657V348.309h51.999V855.69q0 4.616 3.846 8.462 3.847 3.847 8.462 3.847h507.382v51.998H190.309ZM301.999 264v480-480Z"/></svg>';

function initMultiViewUIRoom() {
	let $linkContainer = $('.BaseRoomContents div.defaultColor.styledDiv');
	if($linkContainer.length === 0) {
		return;
	}

	$linkContainer = $linkContainer.eq(0);

	let $link = $('<span><span class="defaultColor" style="margin: 0px 4px;">|</span><span id="cb-enh-add-multi-link" class="link ce-loc" data-ce-loc="multi_add">Add to multi cam viewer<span></span>');
	$linkContainer.append($link);

	$(document).on('click', '#cb-enh-add-multi-link', function() {
		multiAddRoomToList(gCurrentBroadcaster);
	});
}

function initMultiViewUINavigation() {
	let $nav = $('#nav');
	if($nav.length === 0) {
		return;
	}

	let $multiLink = $(multiLinkHTML);
	$nav.append($multiLink);

	let $span = $('<span class="cb-enh-add-cam-icon"></span>');
	$span.addClass('cb-enh-add-cam-icon');
	// $span.addClass('ce-loc');
	// $span.data('ce-loc', 'multi_add');
	$span.prop('title', getLocale('multi_add', 'Add current room to multi cam viewer'));

	let $addIcon = $(multiAddSVG);
	$span.append($addIcon);
	$('#room_list .room_list_room .sub-info .cams').append($span);

	$(document).on('click', '.cb-enh-add-cam-icon', function() {
		let username = $(this).closest('.room_list_room').find('a').eq(0).data('room');
		multiAddRoomToList(username);
	});
}

let multiViewStyle = `
#cb-enh-multi-content h1 {
	font-size: 26px;
	margin-left: 10px;
}

.cb-enh-multi-cam {
	float: left;
	position: relative;

	width: 25%;
	overflow: hidden;
}

.cb-enh-multi-cam video {
	border-radius: unset;
	display: block;

	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	float: left;

	position: absolute;
	max-width: unset;
}

.cb-enh-multi-cam img {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	float: left;
}

.cb-enh-multi-cam .cb-enh-avatar {
	position: absolute;
	top: calc(50% - 150px/2);
	left: calc(50% - 150px/2);
	float: left;
}

.cb-enh-multi-offline img, cb-enh-multi-offline video {
	filter: grayscale(1);
}

#cb-enh-multi-content input[type="button"] {
	text-transform: uppercase;
}

.cb-enh-multi-info {
	clear: both;
	margin: 10px;
}

.cb-enh-multi-info p {
	line-height: 4px;
}

#cb-enh-multi-support {
    clear: both;
    margin: 10px;
	margin-top: 15px;
    font-size: 18px;
    color: white;
    border: 1px solid #496b91;
    padding: 5px;
    border-radius: 4px;
    background: #012247;
	max-width: 750px;
    line-height: 26px;
	cursor: pointer;
}

#cb-enh-multi-share {
	width: 600px;
}

.cb-enh-multi-link {
	cursor: pointer;
}

.cb-enh-multi-username {
	position: absolute;

	bottom: 8px;
	left: 5px;
	font-size: 24px;

	opacity: 0;
	transition: opacity 0.25s;

	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

.cb-enh-multi-username a {
	color: white;
	text-decoration: none;
}

.cb-enh-multi-status {
	position: absolute;
	top: 4px;
	left: 4px;

	opacity: 0;
	transition: opacity 0.25s;

	pointer-events: none;

	color: white;
	text-shadow: rgb(0, 0, 0) 1px 1px 0px, rgb(0, 0, 0) -1px -1px 0px, rgb(0, 0, 0) 1px -1px 0px, rgb(0, 0, 0) -1px 1px 0px;
}

.cb-enh-multi-close {
	position: absolute;
	top: 0;
	right: 0;
	opacity: 0;
	transition: opacity 0.25s;
	cursor: pointer;
}

.cb-enh-multi-close svg path {
	fill: #e80000;
}

.cb-enh-multi-cam:hover .cb-enh-multi-username, .cb-enh-multi-cam:hover .cb-enh-multi-status, .cb-enh-multi-cam:hover .cb-enh-multi-close {
	opacity: 1;
}

.cb-enh-multi-cam-wrap2 {
	padding-top: 56.25%;
	background-color: #090909;
}

#cb-enh-multi-content h1 {
	float: left;
}

#cb-enh-multi-size-selector {
	float: right;
}

.cb-enh-multi-fullscreen {
	overflow: auto;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector {
	position: fixed;
	top: 2;
	right: 50px;
	z-index: 10;
}

.cb-enh-multi-fullscreen h1 {
	display: none;
}

/* hide scrollbar in fullscreen but make it working */
.cb-enh-multi-fullscreen::-webkit-scrollbar {
	display: none;
	overflow: hidden;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector {
	opacity: 0;
	transition: 0.25s opacity;
}

.cb-enh-multi-fullscreen #cb-enh-multi-size-selector:hover {
	opacity: 1;
}

.cb-enh-multi-fullscreen .cb-enh-grid-size-selector-img.cb-enh-first {
	margin-right: 0;
}
`;

let multiViewHTML = `
<div id="cb-enh-multi-content">
	<h1>Chaturbate Enhancer Multi Cam Viewer</h1>
	<div id="cb-enh-multi-size-selector"></div>
	<div style="clear:both;"></div>

	<div id="cb-enh-multi-cams">
	</div>

	<div class="cb-enh-multi-info">
		<br>

		<input type="button" class="ce-loc" id="cb-enh-multi-btn-enter-fullscreen" value="ENTER FULLSCREEN" data-ce-loc="multi_btn_enter_fscreen">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-enter-fullscreen-mini" value="ENTER SEMI FULLSCREEN (WITH ADDRESS BAR)" data-ce-loc="multi_btn_enter_fscreen_mini">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-mute-all" value="MUTE ALL" data-ce-loc="multi_btn_mute_all">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-remove-offline" value="REMOVE OFFLINE CAMS" data-ce-loc="multi_btn_remove_offline">
		<input type="button" class="ce-loc" id="cb-enh-multi-btn-remove-all" value="REMOVE ALL CAMS" data-ce-loc="multi_btn_remove_all">
		
		<br>
		<br>

		<input type="checkbox" id="cb-enh-multi-btn-auto-hide">
		<label for="cb-enh-multi-btn-auto-hide" class="noselect ce-loc" data-ce-loc="multi_auto_hide_offline">Hide offline, private etc. cams</label>

		<br>

		<input type="checkbox" id="cb-enh-multi-btn-auto-remove">
		<label for="cb-enh-multi-btn-auto-remove" class="noselect ce-loc" data-ce-loc="multi_auto_remove_offline">Automatically remove offline cams</label>

		<br>
		<span class="ce-loc" data-ce-loc="max_quality">Max quality</span>:
		<select id="cb-enh-multi-max-quality">
			<option value="2160">2160p</option>
			<option value="1440">1440p</option>
			<option value="1080">1080p</option>
			<option value="720">720p</option>
			<option value="420">480p</option>
		</select>

		<br>
		<br>

		<p>
		<span class="ce-loc" data-ce-loc="multi_tip_can_add">
		You can add cams to this viewer by clicking "Add to multi cam viewer" on room page or by using this icon in room lists</span>: <span id="cb-enh-multi-tip-add-icon"></span>
		</p>

		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_enter_shortcut">Press F11 to enter/exit fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_mini_shortcut">Press F10 to enter/exit semi fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_exit_shortcut">Press ESC to exit fullscreen mode.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_scroll">It's possible to use scroll in fullscreen.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_auto_save">Current viewer state is being automatically saved.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_vid_freeze">Click on the video if it got frozen.</p>
		<p class="ce-loc" data-ce-loc="multi_tip_fscreen_grid_size">It's possible to set grid size on fullscreen too - hover mouse cursor on the right corner to see selectors.</p>

		<br>
		<span class="ce-loc" data-ce-loc="multi_share">Current viewer state can be restored or shared with others (who also have installed Chaturbate Enhancer) via this link</span>:
		<br>
		<input type="text" id="cb-enh-multi-share" readonly>
	</div>

	<div id="cb-enh-multi-support"></div>
</div>
`;

let multiViewCamHTML = `
<div class="cb-enh-multi-cam" data-cb-enh-username="{{username}}">

<div class="cb-enh-multi-cam-wrap">
	<div class="cb-enh-multi-cam-wrap2">
		<div class="cb-enh-multi-cam-img-wrap"></div>
		<div class="cb-enh-multi-cam-video-wrap"></div>

		<div class="cb-enh-multi-username">
			<a href="/{{username}}/" target="_blank" rel="noopener">{{username}}</a>
		</div>

		<div class="cb-enh-multi-status noselect"></div>
		<div class="cb-enh-multi-close noselect">{{closeSVG}}</div>
	</div>
</div>

</div>
`;

let closeSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M256 874.088 181.912 800l224-224-224-224L256 277.912l224 224 224-224L778.088 352l-224 224 224 224L704 874.088l-224-224-224 224Z"/></svg>';

let gMultiRooms = [];
let gMultiRoomsData = [];
let gMultiInMiniFullscreen = false;
let gIntvMultiUpdateRooms;

function initMultiView() {
	gIsInMultiView = true;

	let hash = window.location.hash;
	let roomsParamStart = hash.indexOf('?rooms=');
	if(roomsParamStart !== -1) {
		let rooms = hash.substring(roomsParamStart + '?rooms='.length).split(',');
		multiClearRoomList();
		rooms.forEach(function(room) {
			multiAddRoomToList(room);
		});
	}

	addStyle(multiViewStyle);
	$('#main').append(multiViewHTML);

	$(document).on('click', '.cb-enh-multi-close', function() {
		let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
		multiRemoveRoom(username);
	});

	// Set share URL selected on click
	$(document).on('focus', '#cb-enh-multi-share', function() {
		this.setSelectionRange(0, this.value.length);
	});

	addEventListener('storage', function(e) {
		if(e.key !== 'cb-enh-multicam') {
			return;
		}
		
		multiUpdateRooms();
	});

	multiUpdateRooms();

	// Add size selector icons
	let $selectorDiv = $('#cb-enh-multi-size-selector');
	let $svg1 = $(gGridIconSvgBig);
	$svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
	$selectorDiv.append($svg1);
	$svg1.on('click', function() {
		let width = 2;
		multiSetGridSize(width);
		setSetting('multi-grid-size', width);
	});

	let $svg2 = $(gGridIconSvgMedium);
	$svg2.addClass('cb-enh-grid-size-selector-img');
	$svg2.on('click', function() {
		let columns = 3;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});
	$selectorDiv.append($svg2);

	let $svg3 = $(gGridIconSvgSmall);
	$svg3.addClass('cb-enh-grid-size-selector-img');
	$selectorDiv.append($svg3);
	$svg3.on('click', function() {
		let columns = 4;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});

	let $svg4 = $(gGridIconSvgSmallX);
	$svg4.addClass('cb-enh-grid-size-selector-img');
	$selectorDiv.append($svg4);
	$svg4.on('click', function() {
		let columns = 5;
		multiSetGridSize(columns);
		setSetting('multi-grid-size', columns);
	});
	//

	$(document).on('click', '#cb-enh-multi-btn-remove-offline', function() {
		multiRemoveOfflineRooms();
	});

	$(document).on('click', '#cb-enh-multi-btn-remove-all', function() {
		multiRemoveAllRooms();
	});

	$(document).on('click', '#cb-enh-multi-btn-mute-all', function() {
		$('.cb-enh-multi-cam video').each(function() {
			this.muted = true;
		});
	});

	$(document).on('change', '#cb-enh-multi-btn-auto-remove', function() {
		let enabled = $(this).is(":checked");
		setSetting('multi-auto-remove', enabled);

		if(enabled) {
			multiRemoveOfflineRooms();
		}
	});

	$(document).on('change', '#cb-enh-multi-btn-auto-hide', function() {
		let enabled = $(this).is(":checked");
		setSetting('multi-auto-hide', enabled);
		multiHidePrivateRoomsIfNeeded();
	});

	$(document).on('change', '#cb-enh-multi-max-quality', function() {
		let maxHeight = parseInt($(this).val());
		setSetting('multi-max-quality', maxHeight);

		$('.cb-enh-multi-cam video').each(function() {
			let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
			let hls = gMultiRoomsData[username]['hls'];
			if(hls) {
				multiUpdateVideoQuality(hls, maxHeight);
			}
		});
	});

	let autoRemove = getSetting('multi-auto-remove');
	$('#cb-enh-multi-btn-auto-remove').prop('checked', autoRemove);

	let autoHide = getSetting('multi-auto-hide');
	$('#cb-enh-multi-btn-auto-hide').prop('checked', autoHide);

	$('#cb-enh-multi-max-quality').val(multiGetMaxHeight());

	$(document).on('click', '#cb-enh-multi-btn-enter-fullscreen', function() {
		if(!document.fullscreenElement) {
			multiEnterFullscreen();
		}
		else {
			document.exitFullscreen();
		}
	});

	$(document).on('click', '#cb-enh-multi-btn-enter-fullscreen-mini', function() {
		multiToggleFullscreenMini();
	});

	document.addEventListener('fullscreenchange', multiOnFullscreenChange, false);
	document.addEventListener('mozfullscreenchange', multiOnFullscreenChange, false);
	document.addEventListener('MSFullscreenChange', multiOnFullscreenChange, false);
	document.addEventListener('webkitfullscreenchange', multiOnFullscreenChange, false);

	window.addEventListener('keydown', function(e) {
		let keyCode = e.keyCode || e.which;

		// F11
		if(keyCode === 122) {
			multiEnterFullscreen();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}
		
		// F10
		if(keyCode === 121 && !e.repeat) {
			multiToggleFullscreenMini();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}

		// ESC
		if(keyCode === 27 && gMultiInMiniFullscreen && !e.repeat) {
			multiToggleFullscreenMini();
			e.preventDefault();
			e.stopImmediatePropagation();
			return;
		}
	});

	multiUpdateShareURL();

	// Update room statuses
	clearIntervalEx(gIntvMultiUpdateRooms);
	gIntvMultiUpdateRooms = setInterval(multiUpdateRoomStatuses, 1000 * 30);

	$('#cb-enh-multi-tip-add-icon').append(multiAddSVG);

	// Add support info
	multiInitSupportInfo();

	localizeStringsNextTick();
}

function multiInitSupportInfo() {
	let userType = getUserType();
	let msg = getSupportMessage(userType);
	if(msg === null) {
		return;
	}

	$(document).on('click', '#cb-enh-multi-support', function() {
		loutrreg(gCurrentBroadcaster);
	});

	$('#cb-enh-multi-support').append(msg);
}

function multiUpdateRooms() {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		return;
	}

	if(!'rooms' in data) {
		return;
	}

	data['rooms'].forEach(function(room) {
		if(gMultiRooms.indexOf(room) !== -1) {
			return;
		}

		multiAddRoom(room);
	});
}

function multiUpdateRoomStatuses() {
	gMultiRooms.forEach(function(username) {
		xmlhttpRequest({
			method: 'GET',
			url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
				'Referer': 'https://chaturbate.com/' + username + '/',
			},
			timeout: 60*1*1000,
			onload: function(resp) {
				let data;
				try {
					data = JSON.parse(resp.responseText);
				}
				catch(SyntaxError) {
					return;
				}
	
				if(!'room_status' in data) {
					return;
				}

				gMultiRoomsData[username] = {
					'status': data['room_status']
				};

				let $block = $('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]');
				if($block.length === 0) {
					return;
				}
	
				if(data['room_status'] !== 'public') {
					let statusText = getLocale('status_' + data['room_status'], data['room_status']);
					multiSetErrored($block, statusText);

					if(data['room_status'] === 'offline' && getSetting('multi-auto-remove')) {
						multiRemoveRoom(username);
					}

					else if(getSetting('multi-auto-hide')) {
						$block.hide();
					}

					return;
				}

				if(!('hls_source' in data) || data['hls_source'] === '') {
					return;
				}

				multiSetOk($block);

				let $video = $block.find('video');
				if($video.length === 0) {
					multiInitVideo($block, username, data['hls_source']);
				}
			}
		});
	});
}

function multiSetErrored($block, status) {
	$block.find('.cb-enh-multi-status').text(status);
	$block.addClass('cb-enh-multi-offline');
	$block.find('video').remove();
}

function multiSetOk($block) {
	$block.find('.cb-enh-multi-status').text('');
	$block.removeClass('cb-enh-multi-offline');
	$block.show();
}

function multiInitVideo($block, username, hls_source) {
	let videoHTML = '<video controls autoplay muted data-listener-count-webkitendfullscreen="1" class="vjs-tech cb-enh-video" id="vjs_video_3_html5_api" tabindex="-1" role="application" poster="https://cbjpeg.stream.highwebmedia.com/stream?room={{username}}&f={{random}}"></video>';
	videoHTML = videoHTML.replaceAll('{{username}}', username);
	videoHTML = videoHTML.replaceAll('{{random}}', Math.random());
	let $video = $(videoHTML);

	$video.on('pause', function() {
		makeVideoPlay($video[0]);
	});

	$video.on('click', function() {
		makeVideoPlay($video[0]);
	});

	let hls = new Hls();
	gMultiRoomsData[username]['hls'] = hls;
	
	hls.on(Hls.Events.LEVEL_LOADED, function() {
		multiUpdateVideoQuality(hls);
	});

	hls.loadSource(hls_source);
	hls.attachMedia($video[0]);

	$block.find('.cb-enh-multi-cam-video-wrap').append($video);
}

function multiGetMaxHeight() {
	let maxHeight = getSetting('multi-max-quality');
	if(maxHeight === null) {
		maxHeight = 1080;
	}
	return maxHeight;
}

function multiUpdateVideoQuality(hls) {
	let maxHeight = multiGetMaxHeight();
	let capped = false;

	hls.levels.every(function(level, i) {
		if(level.height > maxHeight) {
			let levelIndex = i - 1;
			if(levelIndex < 0) {
				levelIndex = 0;
			}

			hls.autoLevelCapping = levelIndex;
			capped = true;
			return false;
		}

		return true;
	});

	if(!capped) {
		hls.autoLevelCapping = -1;
	}
}

function multiAddRoom(username) {
	gMultiRooms.push(username);

	let blockHTML = multiViewCamHTML;
	blockHTML = blockHTML.replaceAll('{{username}}', username);
	blockHTML = blockHTML.replaceAll('{{random}}', Math.random());
	blockHTML = blockHTML.replaceAll('{{closeSVG}}', closeSVG);
	let $block = $(blockHTML);

	$('#cb-enh-multi-cams').append($block);

	xmlhttpRequest({
		method: 'GET',
		url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
			'Referer': 'https://chaturbate.com/' + username + '/',
		},
		timeout: 60*1*1000,
		onload: function(resp) {
			let data;
			try {
				data = JSON.parse(resp.responseText);
			}
			catch(SyntaxError) {
				multiSetErrored($block, getLocale('error', 'error'));
				return;
			}

			if(!'room_status' in data) {
				multiSetErrored($block, getLocale('error', 'error'));
				return;
			}

			if($block.length === 0) {
				return;
			}
			
			gMultiRoomsData[username] = {
				'status': data['room_status']
			};

			if(data['room_status'] !== 'public') {
				let statusText = getLocale('status_' + data['room_status'], data['room_status']);
				multiSetErrored($block, statusText);

				if(data['room_status'] === 'offline' && getSetting('multi-auto-remove')) {
					multiRemoveRoom(username);
				}

				else if(getSetting('multi-auto-hide')) {
					$block.hide();
				}
			}
			else if(!('hls_source' in data) || data['hls_source'] === '') {
				multiSetErrored($block,  getLocale('multi_no_video_hls', 'error: no video available') );
			}
			else {
				multiInitVideo($block, username, data['hls_source']);
			}
		},

		onerror: function() {
			if($block.length === 0) {
				return;
			}

			multiSetErrored($block, getLocale('error', 'error'));
		}
	});

	fetchRoomSnapshot(username, function(resp) {
		if($block.length === 0) {
			return;
		}

		let $img = $('<img>');
		$img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
		$block.find('.cb-enh-multi-cam-img-wrap').append($img);
	});

	insertRoomAv($block.find('.cb-enh-multi-cam-img-wrap'), username);
	multiUpdateShareURL();
}

function multiRemoveRoom(username) {
	$('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]').remove();

	let pos = gMultiRooms.indexOf(username);
	if(pos !== -1) {
		gMultiRooms.splice(pos, 1);
		delete gMultiRoomsData[username];
	}

	let data = {
		'rooms': gMultiRooms
	};

	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
	multiUpdateShareURL();
}

function multiAddRoomToList(username) {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		data = null;
	}
	
	if(!data) {
		data = {
			'rooms': []
		};
	}

	if(data['rooms'].indexOf(username) !== -1) {
		return;
	}

	data['rooms'].push(username);
	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
	multiUpdateShareURL();
}

function multiClearRoomList() {
	let data = localStorage.getItem('cb-enh-multicam');
	try {
		data = JSON.parse(data);
	}
	catch(SyntaxError) {
		data = null;
	}
	
	data['rooms'] = [];
	localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
}

function multiSetGridSize(columns) {
	let width = 100 / columns;
	let gridStyle = `
	.cb-enh-multi-cam {
		width: ` + width + `% !important;
	}
	`;
	addStyle(gridStyle);
}

// Set room grid size from user settings
function multiLoadGridSize() {
	let minGridSize = 2;
	let maxGridSize = 10;
	
	let size = getSetting('multi-grid-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			multiSetGridSize(minGridSize)
		}
	
		else if(size > maxGridSize) {
			multiSetGridSize(maxGridSize)
		}
	
		else {
			multiSetGridSize(size);
		}
	}
}

function multiRemoveOfflineRooms() {
	$('.cb-enh-multi-offline').each(function() {
		let username = $(this).data('cb-enh-username');
		if(gMultiRoomsData[username]['status'] !== 'offline') {
			return;
		}

		multiRemoveRoom(username);
	});
}

function multiHidePrivateRoomsIfNeeded() {
	let autoHide = getSetting('multi-auto-hide');
	$('.cb-enh-multi-cam').each(function() {
		let username = $(this).data('cb-enh-username');
		let status = gMultiRoomsData[username]['status'];
		if(status !== 'public' && autoHide) {
			$(this).hide();
		}
		else {
			$(this).show();
		}
	});
}

function multiRemoveAllRooms() {
	$('.cb-enh-multi-cam').each(function() {
		let username = $(this).data('cb-enh-username');
		multiRemoveRoom(username);
	});
}

function multiUpdateShareURL() {
	let url = 'https://chaturbate.com/#multicam';
	if(gMultiRooms.length !== 0) {
		url += '?rooms=' + gMultiRooms.join(',');
	}
	$('#cb-enh-multi-share').val(url);
}

function multiEnterFullscreen() {
	if(document.fullscreenElement) {
		return;
	}

	$('#cb-enh-multi-content')[0].requestFullscreen();
	$('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');
	$('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
}

function multiToggleFullscreenMini() {
	let $btn = $('#cb-enh-multi-btn-enter-fullscreen-mini');

	if(!gMultiInMiniFullscreen) {
		$('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');

		$('#header').hide();
		$('.top-section').hide();
		$('#footer-holder').hide();

		$btn.val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
	}
	else {
		$('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');

		$('#header').show();
		$('.top-section').show();
		$('#footer-holder').show();

		$btn.val( getLocale('multi_btn_enter_fscreen_mini', 'ENTER SEMI FULLSCREEN (WITH ADDRESS BAR)') );
	}

	gMultiInMiniFullscreen = !gMultiInMiniFullscreen;
	window.scrollTo(0, 0);
}

function multiOnFullscreenChange() {
	if(!document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
		// Fullscreen exit
		$('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');
		$('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_enter_fscreen', 'ENTER FULLSCREEN') );
	}
}
// MULTI VIEW END

// UTILS BELOW
function isInteractiveFullScreenEnabled() {
	if(!$('#vjs_video_3').hasClass('vjs-controls-disabled')) {
		return false;
	}

	let $player = $('.videoPlayerDiv');
	return $player.innerWidth() === window.innerWidth && $player.innerHeight() === window.innerHeight;
}

function enterInteractiveFullScreen() {
	$('#fvm-link').click();
}

function isTheaterModeEnabled() {
	return $('#vjs_video_3').hasClass('vjs-controls-disabled');
}

function getFileName(fname, ext, date) {
	let d = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
	let t = ('0' + date.getHours()).slice(-2) + '-' + ('0' + date.getMinutes()).slice(-2) + '-' + ('0' + date.getSeconds()).slice(-2);
	return fname + '_' + d + '_' + t + ext;
}

function captureStream(el) {
	if(el.captureStream) {
		return el.captureStream();
	}

	if(el.mozCaptureStream) {
		return el.mozCaptureStream();
	}

	return 'err-no-func';
}

// Clear interval and null it
function clearIntervalEx(intv) {
	if(intv) {
		clearInterval(intv);
		intv = null;
	}
}

function rev(str) {
    let rv = '';
    for(let i = str.length - 1; i >= 0; i--) {
		rv += str[i];
    }
    return rv;
}

// Start
loadSettings();

if(getSetting('reg-redir') === 1) {
	regRedir(getSetting('reg-redir-room'));
}

document.addEventListener('click', function(e) {
	if(regRedirPreventClick && e.target !== $('.logo-zone a')[0]) {
		e.stopImmediatePropagation();
		e.preventDefault();
		return;
	}

	// Enter interactive fullscreen via video fullscreen button if configured
	if(e.target === $('button.vjs-fullscreen-control > span.vjs-icon-placeholder')[0] || e.target === $('#full-screen')[0]) {
		if(videoJsPlayer && !videoJsPlayer.isFullscreen() && getSetting('use-intrf-def')) {
			e.preventDefault();
			e.stopImmediatePropagation();
			enterInteractiveFullScreen();
		}
		return;
	}

	// Disable pausing on video click
	let vid = getVideo();
	if(vid && e.target === vid[0] && isVideoPlaying(vid[0])) {
		e.stopImmediatePropagation();

		// Simulate body click so things like followers tab will close properly
		document.body.click();
	}
}, true);

document.addEventListener('dblclick', function(e) {
	let vid = getVideo();
	if(!vid || e.target !== vid[0]) {
		return;
	}

	if(isInteractiveFullScreenEnabled()) {
		// Exit interactive fullscreen on double click
		$('span#full-screen').click();
		return;
	}

	if(isTheaterModeEnabled() && videoJsPlayer && !videoJsPlayer.isFullscreen()) {
		if(getSetting('use-intrf-def')) {
			e.preventDefault();
			e.stopImmediatePropagation();
			enterInteractiveFullScreen();
			return;
		}

		// Enter theater mode fullscreen on double click
		videoJsPlayer.requestFullscreen();
		return;
	}

	if(videoJsPlayer && !videoJsPlayer.isFullscreen() && getSetting('use-intrf-def')) {
		e.preventDefault();
		e.stopImmediatePropagation();
		enterInteractiveFullScreen();
		return;
	}
}, true);

// Set room grid size from user settings
function loadGridSize() {
	let minGridSize = 50;
	let maxGridSize = 300;
	
	let size = getSetting('grid-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			setGridSize2(minGridSize)
		}
	
		else if(size > maxGridSize) {
			setGridSize2(maxGridSize)
		}
	
		else {
			setGridSize2(size);
		}
	}
}
loadGridSize();
// End of set room grid size from user settings

// Set more room grid size from user settings
function loadMoreGridSize() {
	let minGridSize = 50;
	let maxGridSize = 300;

	let size = getSetting('grid-more-size');
	size = parseInt(size);
	if(Number.isInteger(size)) {
		if(size < minGridSize) {
			setMoreRoomsGridSize(minGridSize)
		}
	
		else if(size > maxGridSize) {
			setMoreRoomsGridSize(maxGridSize)
		}
	
		else {
			setMoreRoomsGridSize(size);
		}
	}
}
loadMoreGridSize();
// End of set more room grid size from user settings

multiLoadGridSize();

function fetchRoomSnapshot(username, callback) {
	let req = new XMLHttpRequest();
	req.timeout = 10000;
	req.responseType = 'arraybuffer';
	req.addEventListener('load', function() {
		let length = req.getResponseHeader('Content-length');
		if(length === '4824' || length === '4456') {
			// Generic offline thumbnail
			return;
		}

		callback(req.response);
	});

	req.open('GET', 'https://roomimg.stream.highwebmedia.com/riw/' + username + '.jpg');
	req.send();
}

})();