Chaturbate Enhancer

Enhances Chaturbate by adding multiple new features.

Mint 2022.07.30.. Lásd a legutóbbi verzió

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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'
	});
}

})();