您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
通过添加多个新功能来增强 Chaturbate。
当前为
// ==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.2.6 // @author improper.dev // @license CC-BY-ND-4.0 // @copyright improper.dev (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/[email protected]/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; line-height: 15px; cursor: pointer; } .cb-enh-acc-info-small-text { font-size: 13px; line-height: 13px; } #cb-enh-multi-support .cb-enh-acc-info-small-text { margin-top: 4px; font-size: 15px; line-height: 17px; } .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, .cb-enh-multi-fullscreen #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']; localizeStrings(); } }); } 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 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; } 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> ❤️. <span class="ce-loc" data-ce-loc="ac_msg_m_0"> If you create new account we will get 20% commision of every tokens spend by you. It cost you nothing but it will significantly help further development of Chaturbate Enhancer. This message will disappear. <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> ❤️. <div class="cb-enh-acc-info-small-text"> <span class="ce-loc" data-ce-loc="ac_msg_m_0"> If you create new account we will get 20% commision of every tokens spend by you. It cost you nothing but it will significantly help further development of Chaturbate Enhancer. This message will disappear. <span> </div> `; } 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 // Returns room page video element function getVideo() { return $(".videoPlayerDiv video"); } function isVideoPlaying(vid) { return !vid.paused && !vid.ended && vid.readyState > 2; } function playIgnoreErrors(vid) { vid.play().catch(() => { () => {} // noop }); } 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); 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; } playIgnoreErrors($vid[0]); 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); } // // Set grid style by injecting new CSS style (so it can be done before page render) function setGridSize(width) { let gridStyle = ` #room_list { grid-template-columns: repeat(auto-fill,minmax(` + width + `px,max-content)) !important; } `; 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 multiAddUIAddButtons() { $('ul.list li.roomCard').each(function() { if($(this).data('cb-enh-multi-icon-added')) { return; } $(this).data('cb-enh-multi-icon-added', true); 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 to multi cam viewer')); let $addIcon = $(multiAddSVG); $span.append($addIcon); $(this).find('.sub-info .cams').append($span); }); } function initMultiViewUINavigation() { let $nav = $('#nav'); if($nav.length !== 0) { let $multiLink = $(multiLinkHTML); $nav.eq(0).append($multiLink); } $(document).on('click', '.cb-enh-add-cam-icon', function() { let username = $(this).closest('.roomCard').find('a').eq(0).data('room'); if(username) { multiAddRoomToList(username); } }); multiAddUIAddButtons(); setInterval(function() { multiAddUIAddButtons(); }, 1000); } let multiViewStyle = ` #cb-enh-multi-content h1 { font-size: 26px; margin-left: 10px; } .cb-enh-multi-cam { display: none; 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-drag-outline { opacity: 0; outline: 3px dashed #eb3400; outline-offset: -3px; position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 10; pointer-events: none; transition: 0.2s opacity; } .cb-enh-multi-cam.cb-enh-multi-cam-ondragover .cb-enh-multi-drag-outline { opacity: 1; } .cb-enh-multi-offline img, cb-enh-multi-offline video { filter: grayscale(1); } /* draggable snapshot */ img.cb-enh-multi-offline { filter: grayscale(1); } #cb-enh-multi-content input[type="button"] { text-transform: uppercase; cursor: pointer; } #cb-enh-multi-content input[type="checkbox"], #cb-enh-multi-content label { cursor: pointer; } .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-color: #003168; max-width: 750px; line-height: 21px; cursor: pointer; transition: 0.2s background-color; } .darkmode #cb-enh-multi-support { background-color: #012247; } #cb-enh-multi-support:hover { background-color: #005ac0; } #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-refresh { position: absolute; top: 1px; right: 24px; opacity: 0; transition: opacity 0.25s; cursor: pointer; display: none; } .cb-enh-multi-refresh svg path { fill: #1e1e1e; } .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, .cb-enh-multi-cam:hover .cb-enh-multi-refresh { 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; } .cb-enh-multi-cam-dragging { opacity: 0.7; } #cb-enh-multi-drag-img { position: fixed; display: none; top: 0; left: 0; width: 400px; height: 225px; background-color: black; pointer-events: none; z-index: 5000; opacity: 0.9; } #cb-enh-multi-drag-img canvas, #cb-enh-multi-drag-img img { width: 100%; height: 100%; } #cb-enh-multi-drag-img, #cb-enh-multi-drag-img canvas { border-radius: 10px; } `; 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_drag">Simply drag & drop cams to reposition them.</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_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 id="cb-enh-multi-drag-img"></div> </div> `; let multiViewCamHTML = ` <div class="cb-enh-multi-cam" data-cb-enh-username="{{username}}" draggable="true"> <div class="cb-enh-multi-drag-outline"></div> <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-refresh noselect">{{refreshSVG}}</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 refreshSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M477.739 915.218q-141.348 0-240.283-98.935Q138.521 717.348 138.521 576q0-141.348 98.935-240.283 98.935-98.935 240.283-98.935 74.087 0 140.761 30.195 66.674 30.196 113.935 87.153V236.782h89.044v307.697H513.217V456h164.609q-32-52.609-84.957-82.913-52.956-30.304-115.13-30.304-97.174 0-165.195 68.022Q244.522 478.826 244.522 576q0 97.174 68.022 165.195 68.021 68.022 165.195 68.022 73.609 0 133.63-42.304Q671.391 724.609 696.956 656h110.567q-28.566 113.913-120.218 186.566-91.653 72.652-209.566 72.652Z"/></svg>'; let gMultiRooms = []; let gMultiRoomsData = []; let gMultiInMiniFullscreen = false; let gIntvMultiUpdateRooms; let gMultiIsDragging = false; 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 && !e.repeat) { if(gMultiInMiniFullscreen) { multiToggleFullscreenMini(); e.preventDefault(); e.stopImmediatePropagation(); return; } if(gMultiIsDragging) { multiOnDragEnd(true) e.preventDefault(); e.stopImmediatePropagation(); return; } } }); multiUpdateShareURL(); // Update room statuses clearIntervalEx(gIntvMultiUpdateRooms); gIntvMultiUpdateRooms = setInterval(multiUpdateRoomStatuses, 1000 * 30); $('#cb-enh-multi-tip-add-icon').append(multiAddSVG); // Drag & drop let cursorX = 0; let cursorY = 0; // Current block we move let $dragBlock = null; // Current block we hover on let $dragHoverBlock = null; function updateDragImagePosition() { let $dragImg = $('#cb-enh-multi-drag-img'); let width = $dragImg.outerWidth(); let height = $dragImg.outerHeight(); $dragImg.css('left', cursorX - width / 2); $dragImg.css('top', cursorY - height / 2 - window.scrollY); } addEventListener('mousemove', function(e) { cursorX = e.pageX; cursorY = e.pageY; if(gMultiIsDragging) { updateDragImagePosition(); } }); function multiDrop() { let oldScrollY = window.scrollY; let index = $dragHoverBlock.index(); let oldIndex = $dragBlock.index(); let arrMin1 = false; if(index === 0) { $dragBlock.prependTo($('#cb-enh-multi-cams')); } else { if(index > oldIndex) { index += 1; arrMin1 = true; } $dragBlock.insertAfter($('.cb-enh-multi-cam').eq(index - 1)); } if(arrMin1) { index -= 1; } arraymove(gMultiRooms, oldIndex, index); // Restore old scroll position window.scrollTo(0, oldScrollY); // Update data in local storage let data = localStorage.getItem('cb-enh-multicam'); try { data = JSON.parse(data); } catch(SyntaxError) { data = null; } if(!data) { data = { 'rooms': [] }; } data['rooms'] = gMultiRooms; localStorage.setItem('cb-enh-multicam', JSON.stringify(data)); multiUpdateShareURL(); } function multiOnDragEnd(cancelled) { // Drag end gMultiIsDragging = false; let $dragImg = $('#cb-enh-multi-drag-img'); $dragImg.hide(); $dragImg.html(''); $dragBlock.removeClass('cb-enh-multi-cam-dragging'); $('.cb-enh-multi-cam').removeClass('cb-enh-multi-cam-ondragover'); document.body.style.cursor = ''; // Drop if(!cancelled && $dragHoverBlock && $dragHoverBlock.length !== 0 && !$dragBlock.is($dragHoverBlock)) { multiDrop(); } $dragBlock.removeClass('cb-enh-multi-cam-dragging'); $dragBlock = null; $dragHoverBlock = null; } addEventListener('pointerup', function(e) { if(!gMultiIsDragging) { return; } multiOnDragEnd(false); }); $(document).on('dragstart', '.cb-enh-multi-cam', function(e) { e = e.originalEvent; e.preventDefault(); e.stopImmediatePropagation(); $dragBlock = $(e.target).closest('.cb-enh-multi-cam'); $dragBlock.addClass('cb-enh-multi-cam-dragging'); let $dragImg = $('#cb-enh-multi-drag-img'); let video = $dragBlock.find('video')[0]; if(!video) { // video = $dragBlock.find('img.cb-enh-multi-cam-snapshot')[0]; let $img = $dragBlock.find('img.cb-enh-multi-cam-snapshot'); if($img.length > 0) { $img = $img.clone(); if($dragBlock.hasClass('cb-enh-multi-offline')) { $img.addClass('cb-enh-multi-offline'); } $dragImg.html($img); } } else { let canvas = captureVideoFrame(video); $dragImg.html(canvas); } gMultiIsDragging = true; updateDragImagePosition(); $dragImg.show(); document.body.style.cursor = 'move'; }); $(document).on('mouseenter', '.cb-enh-multi-cam', function() { if(!gMultiIsDragging) { return; } let $parent = $(this).closest('.cb-enh-multi-cam'); if(!$dragBlock.is($parent)) { $($parent).addClass('cb-enh-multi-cam-ondragover'); $dragHoverBlock = $parent; } else { $dragHoverBlock = null; } }); $(document).on('mouseleave', '.cb-enh-multi-cam', function() { if(!gMultiIsDragging) { return; } let $parent = $(this).closest('.cb-enh-multi-cam'); $($parent).removeClass('cb-enh-multi-cam-ondragover'); if(!$dragBlock.is($parent)) { $dragHoverBlock = null; } }); // 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(); if(getSetting('multi-auto-hide')) { $block.hide(); } else { $block.show(); } } 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 '; if(!isFirefox()) { videoHTML += 'controls '; } videoHTML += '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); 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); // blockHTML = blockHTML.replaceAll('{{refreshSVG}}', refreshSVG); 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' && getSetting('multi-auto-hide')) { $block.hide(); } else { $block.show(); } 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(!('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')); if(getSetting('multi-auto-hide')) { $block.hide(); } else { $block.show(); } } }); fetchRoomSnapshot(username, function(resp) { if($block.length === 0) { return; } let $img = $('<img>'); $img.addClass('cb-enh-multi-cam-snapshot'); $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; } function arraymove(arr, fromIndex, toIndex) { let element = arr[fromIndex]; arr.splice(fromIndex, 1); arr.splice(toIndex, 0, element); } function isFirefox() { return navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; } // 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 if(e.target.nodeName === 'VIDEO' && isVideoPlaying(e.target)) { e.stopImmediatePropagation(); e.preventDefault(); // 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]) { // Reimplement video fullscreen enter/exit on double click if(e.target.nodeName === 'VIDEO') { if(document.fullscreenElement && document.fullscreenElement.nodeName === 'VIDEO') { document.exitFullscreen(); } else { e.target.requestFullscreen(); } } 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) { setGridSize(minGridSize) } else if(size > maxGridSize) { setGridSize(maxGridSize) } else { setGridSize(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(); } })();