Chaturbate Enhancer

Enhances Chaturbate by adding multiple new features.

Versión del día 30/7/2022. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name             Chaturbate Enhancer
// @description      Enhances Chaturbate by adding multiple new features.
// @version          1.2.3
// @author           MoonDivision
// @license          CC-BY-ND-4.0
// @copyright        MoonDivision (https://sleazyfork.org/en/users/884016-moondivision)
// @namespace        https://sleazyfork.org/en/users/884016-moondivision
// @homepage         https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer
// @supportURL       https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer/feedback
// @contributionURL  https://chaturbate.com/in/?tour=JpRf&campaign=Nb8Yz&track=enh-contrib&next=/tipping/purchase_tokens/
// @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
// @grant            GM_addStyle
// @grant            GM_addElement
// @grant            GM_xmlhttpRequest
// @require          https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require          https://cdn.jsdelivr.net/npm/hls.js@1
// @run-at           document-body
// @noframes
// ==/UserScript==

(function() {
'use strict';

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-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 {
	position: unset !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;
}

.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-inac-load-chat {
	cursor: pointer;
}
`;

GM_addStyle(style);

$(document).ready(function() {
	if('initialRoomDossier' in unsafeWindow) {
		enhanceRoom();
	}

	if(!$('#id_animate_thumbnails').is(':checked')) {
		$(document).on('mouseenter', '.room_list_room img, .roomElement img, .roomCard img', function() {
			if(window.currentHoverInterval) {
				clearInterval(window.currentHoverInterval);
				window.currentHoverInterval = null;
			}

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

		$(document).on('mouseleave', '.room_list_room img, .roomElement img, .roomCard img', function() {
			if(window.currentHoverInterval) {
				clearInterval(window.currentHoverInterval);
				window.currentHoverInterval = null;
			}
		});
	}
});

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

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) {
	$('.cb-enh-row').remove();
	let lang = $('html').attr('lang');

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

		// Display video of inaccessible room
		let $baseRoomContentDiv =  $("div.BaseRoomContents div")
		if($baseRoomContentDiv.length > 0) {
			if($baseRoomContentDiv.text().indexOf("Access denied") === 0) {
				$baseRoomContentDiv.append("<br>Chaturbate Enhancer will try to display video of this room.<br><br>");

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

					GM_addStyle(`
						.BaseRoomContents div {
							font-size: 14px !important;
						}
					`);

					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
					GM_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(responseDetails) {
							let data;
							try {
								data = JSON.parse(responseDetails.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('Room status is: ' + data['room_status'] + '<br>');
								playVideo = false;
							}
							else {
								isOnline = true;
								$("#cb-enh-inac-load-chat").show()
							}

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

							if(data['room_title']) {
								if(data['room_status'] !== 'offline') {
									$upperHolder.append('Subject: ' + data['room_title'] + '<br><br>');
								}
								else {
									$upperHolder.append('Last Subject: ' + data['room_title'] + '<br><br>');
								}
							}

							let $video;
							if(playVideo) {
								$video = $('<video controls webkit-playsinline playsinline 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() {
									try {
										$video[0].play();
									} catch(err) {}
								});

								$video.on('click', function() {
									try {
										$video[0].play();
									} catch(err) {}
								});

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

							if(data['age']) {
								$upperHolder3.append('<br><br>Age: ' + data['age'] + '<br>');
							}

							if(data['broadcaster_gender']) {
								$upperHolder3.append('Gender: ' + data['broadcaster_gender'] + '<br>');
							}

							if(data['num_viewers']) {
								if(data['room_status'] !== 'offline') {
									$upperHolder3.append('Viewers: ' + data['num_viewers'] + '<br>');
								}
								else {
									$upperHolder3.append('Last Viewers: ' + data['num_viewers'] + '<br>');
								}
							}
							
							if(data['performer_has_fanclub']) {
								$infoHolder2.append('Has Fanclub: Yes<br>');
							}
							else {
								$infoHolder2.append('Has Fanclub: No<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('Satisfaction Score: ' + sc['percent'] + '% (' + sc['up_votes'] + ' up, ' + sc['down_votes'] + ' down)<br>');
								}
							}
						},
						onerror: function() {
							$upperHolder.append('<br>ERROR: Unable to load video.');
						}
					});

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

							let $loadChatHref = $('<a id="cb-enh-inac-load-chat" style="display:none;">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();
								GM_addElement($videoHolder[0], 'iframe', {
									src: 'https://onechance.onelove.workers.dev/?https://chaturbate.com/embed/' + username + '/',
									style: 'width: 100%; max-width: 1400px; height: 700px; border: 0; border-radius: 4px;',
								});
							});

							// 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('Region: <a href="' + href + '">' + data['region'] + '</a><br>');
							}

							// Populate "online for" row
							if(data['online_for'] && data['online_for'] !== '') {
								$infoHolder.append('Online For: ' + data['online_for'] + '<br>');
							}
							else if(data['last_online'] && data['last_online'] !== '') {
								$infoHolder.append('Last Online: ' + data['last_online'] + '<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]) {
									$infoHolder.append(v + ': ' + data[k] + '<br>');
								}
							});

							// Add schedule
							if(data['has_schedule']) {
								$scheduleHolder.append('Schedule: <br>');
								let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
								GM_addElement($scheduleHolder[0], 'iframe', {
									src: 'https://camschedule.com/embed/schedule/' + username + '?dark=' + darkMode + '&lang=' + lang,
									style: 'width: 100%; height: 350px; border: 0;',
								});
							}
						}
					});
				}
			}
		}
		return;
	}

	let intv = setInterval(function() {
		if($('video.vjs-tech').length > 0) {
			// Make clicking on live video feed don't pause it anymore
			$('video.vjs-tech').on('pause', function() {
				try {
					$('video.vjs-tech')[0].play();
				} catch(err) {}
			});
			clearInterval(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) {
					clearInterval(pageTransitionIntv);
					enhanceRoom(true);
					currentUsername = uname;
				}	
			}, 25);
		}
	}, 25);

	let userData;
	let broadcasterName;
	if(!ajaxTransition) {
		userData = JSON.parse(unsafeWindow.initialRoomDossier);
		broadcasterName = userData.broadcaster_username;
	}
	else {
		broadcasterName = $("a.nextCamBgColor")[0].getAttribute('href').slice(6, -1);
	}

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

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

		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);

		GM_xmlhttpRequest({
			method: 'GET',
			url: 'https://camschedule.com/api/room/' + broadcasterName + '?lang=' + lang,
			timeout: 60*2*1000,
			onload: function(responseDetails) {
				let data;
				try {
					data = JSON.parse(responseDetails.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'] !== '') {
					$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');
					GM_addElement(iframeWrapper, 'iframe', {
						src: 'https://camschedule.com/embed/schedule/' + broadcasterName + '?dark=' + darkMode + '&lang=' + lang,
						style: 'width: 100%; height: 350px; border: 0;',
					});
					$divSchedule.show();
				}
			}
		});
	}, 500);
}

function addBioRow(name, visible = true, value = '') {
	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>' + name + ':</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;
}

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

	GM_addElement($('.cb-enh-avatar')[0], 'img', {
		src: 'https://thumbv.camschedule.com/av/' + username + '.jpg',
		alt: '',
		onload: 'this.style.opacity=1'
	});
}

})();