Chaturbate Enhancer

משפר את Chaturbate על ידי הוספת תכונות חדשות מרובות.

  1. // ==UserScript==
  2. // @name Chaturbate Enhancer
  3. // @name:de Chaturbate Enhancer
  4. // @name:es Chaturbate Enhancer
  5. // @name:es-CO Chaturbate Enhancer
  6. // @name:it Chaturbate Enhancer
  7. // @name:fr Chaturbate Enhancer
  8. // @name:fr-CA Chaturbate Enhancer
  9. // @name:ru Chaturbate Enhancer
  10. // @name:tr Chaturbate Enhancer
  11. // @name:ro Chaturbate Enhancer
  12. // @name:no Chaturbate Enhancer
  13. // @name:nl Chaturbate Enhancer
  14. // @name:pl Chaturbate Enhancer
  15. // @name:ja Chaturbate Enhancer
  16. // @name:el Chaturbate Enhancer
  17. // @name:hu Chaturbate Enhancer
  18. // @name:fi Chaturbate Enhancer
  19. // @name:ar Chaturbate Enhancer
  20. // @name:hi Chaturbate Enhancer
  21. // @name:id Chaturbate Enhancer
  22. // @name:ko Chaturbate Enhancer
  23. // @name:pt-PT Chaturbate Enhancer
  24. // @name:pt-BR Chaturbate Enhancer
  25. // @name:zh Chaturbate Enhancer
  26. // @name:zh-CN Chaturbate Enhancer
  27. // @name:zh-TW Chaturbate Enhancer
  28. // @name:cs Chaturbate Enhancer
  29. // @name:sk Chaturbate Enhancer
  30. // @name:sl Chaturbate Enhancer
  31. // @name:sv Chaturbate Enhancer
  32. // @name:sr Chaturbate Enhancer
  33. // @name:af Chaturbate Enhancer
  34. // @name:sq Chaturbate Enhancer
  35. // @name:hy Chaturbate Enhancer
  36. // @name:be Chaturbate Enhancer
  37. // @name:bg Chaturbate Enhancer
  38. // @name:da Chaturbate Enhancer
  39. // @name:et Chaturbate Enhancer
  40. // @name:he Chaturbate Enhancer
  41. // @name:hr Chaturbate Enhancer
  42. // @name:fa Chaturbate Enhancer
  43. // @name:ur Chaturbate Enhancer
  44. // @name:bn Chaturbate Enhancer
  45. // @name:th Chaturbate Enhancer
  46. // @name:eo Chaturbate Enhancer
  47. // @name:ug Chaturbate Enhancer
  48. // @name:vi Chaturbate Enhancer
  49. // @description Enhances Chaturbate by adding multiple new features.
  50. // @description:de Verbessert Chaturbate durch Hinzufügen mehrerer neuer Funktionen.
  51. // @description:es Mejora Chaturbate al agregar múltiples funciones nuevas.
  52. // @description:es-CO Mejora Chaturbate al agregar múltiples funciones nuevas.
  53. // @description:it Migliora Chaturbate aggiungendo più nuove funzionalità.
  54. // @description:fr Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
  55. // @description:fr-CA Améliore Chaturbate en ajoutant plusieurs nouvelles fonctionnalités.
  56. // @description:ru Улучшает Chaturbate, добавляя несколько новых функций.
  57. // @description:tr Birden çok yeni özellik ekleyerek Chaturbate'i geliştirir.
  58. // @description:ro Îmbunătățește Chaturbate prin adăugarea de mai multe funcții noi.
  59. // @description:no Forbedrer Chaturbate ved å legge til flere nye funksjoner.
  60. // @description:nl Verbetert Chaturbate door meerdere nieuwe functies toe te voegen.
  61. // @description:pl Ulepsza Chaturbate, dodając wiele nowych funkcji.
  62. // @description:ja 複数の新機能を追加して Chaturbate を強化します。
  63. // @description:el Βελτιώνει το Chaturbate προσθέτοντας πολλές νέες δυνατότητες.
  64. // @description:hu Több új funkció hozzáadásával továbbfejleszti a Chaturbate szolgáltatást.
  65. // @description:fi Parantaa Chaturbatea lisäämällä useita uusia ominaisuuksia.
  66. // @description:ar يعزز Chaturbate عن طريق إضافة ميزات جديدة متعددة.
  67. // @description:hi कई नई सुविधाओं को जोड़कर Chaturbate को बेहतर बनाता है।
  68. // @description:id Meningkatkan Chaturbate dengan menambahkan beberapa fitur baru.
  69. // @description:ko 여러 새로운 기능을 추가하여 Chaturbate를 향상시킵니다.
  70. // @description:pt-PT Aprimora o Chaturbate adicionando vários novos recursos.
  71. // @description:pt-BR Aprimora o Chaturbate adicionando vários novos recursos.
  72. // @description:zh 通过添加多个新功能来增强 Chaturbate。
  73. // @description:zh-CN 通过添加多个新功能来增强 Chaturbate。
  74. // @description:zh-TW 通过添加多个新功能来增强 Chaturbate。
  75. // @description:cs Vylepšuje Chaturbate přidáním několika nových funkcí.
  76. // @description:sk Vylepšuje Chaturbate pridaním viacerých nových funkcií.
  77. // @description:sl Izboljša Chaturbate z dodajanjem več novih funkcij.
  78. // @description:sv Förbättrar Chaturbate genom att lägga till flera nya funktioner.
  79. // @description:sr Побољшава Цхатурбате додавањем више нових функција.
  80. // @description:af Verbeter Chaturbate deur verskeie nuwe kenmerke by te voeg.
  81. // @description:sq Përmirëson Chaturbate duke shtuar veçori të shumta të reja.
  82. // @description:hy Ընդլայնում է Chaturbate-ը՝ ավելացնելով բազմաթիվ նոր հնարավորություններ:
  83. // @description:be Паляпшае Chaturbate шляхам дадання некалькіх новых функцый.
  84. // @description:bg Подобрява Chaturbate чрез добавяне на множество нови функции.
  85. // @description:da Forbedrer Chaturbate ved at tilføje flere nye funktioner.
  86. // @description:et Täiustab Chaturbate'i, lisades mitu uut funktsiooni.
  87. // @description:he משפר את Chaturbate על ידי הוספת תכונות חדשות מרובות.
  88. // @description:hr Poboljšava Chaturbate dodavanjem više novih značajki.
  89. // @description:fa Chaturbate را با افزودن چندین ویژگی جدید تقویت می کند.
  90. // @description:ur متعدد نئی خصوصیات شامل کرکے Chaturbate کو بہتر بناتا ہے۔
  91. // @description:bn একাধিক নতুন বৈশিষ্ট্য যোগ করে Chaturbate উন্নত করে।
  92. // @description:th ปรับปรุง Chaturbate ด้วยการเพิ่มคุณสมบัติใหม่หลายอย่าง
  93. // @description:eo Plibonigas Chaturbate aldonante plurajn novajn funkciojn.
  94. // @description:ug كۆپ خىل يېڭى ئىقتىدارلارنى قوشۇش ئارقىلىق Chaturbate نى كۈچەيتىدۇ.
  95. // @description:vi Cải thiện Chaturbate bằng cách thêm nhiều tính năng mới.
  96. // @version 5.1.3
  97. // @author improper.dev
  98. // @license CC-BY-ND-4.0; https://creativecommons.org/licenses/by-nd/4.0/legalcode
  99. // @copyright improper.dev (https://improper.dev/)
  100. // @namespace https://improper.dev/
  101. // @homepage https://cb-enh.improper.dev/
  102. // @supportURL https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer/feedback
  103. // @contributionURL https://cb-enh.improper.dev/contribute
  104. // @icon https://www.google.com/s2/favicons?sz=32&domain=chaturbate.com
  105. // @icon64 https://www.google.com/s2/favicons?sz=64&domain=chaturbate.com
  106. // @match https://chaturbate.com/*
  107. // @match https://*.chaturbate.com/*
  108. // @exclude https://chaturbate.com/security/*
  109. // @exclude https://*.chaturbate.com/security/*
  110. // @connect cb-enh.improper.dev
  111. // @connect cb-enh-api.improper.dev
  112. // @connect cb-enh-api2.improper.dev
  113. // @connect cb-enh-thumb.improper.dev
  114. // @grant GM_addStyle
  115. // @grant GM_addElement
  116. // @grant GM_xmlhttpRequest
  117. // @grant GM_registerMenuCommand
  118. // @grant GM_unregisterMenuCommand
  119. // @grant GM_setClipboard
  120. // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
  121. // @require https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js
  122. // @run-at document-start
  123. // @noframes
  124. // ==/UserScript==
  125. /*
  126. Chaturbate™ Enhancer v5.1.3 © improper.dev
  127. Please do not share modified version of this script without permission. We have spent significant time and effort on developing this script.
  128. Instead, you are welcome to contribute to the main script code. You can submit code patches, bug reports and ideas at https://sleazyfork.org/en/scripts/441079-chaturbate-enhancer/feedback or via e-mail: contact@improper.dev
  129. You will be credited for your contributions.
  130. Thank you!
  131. */
  132. /*
  133. Attribution-NoDerivatives 4.0 International
  134. =======================================================================
  135. Creative Commons Corporation ("Creative Commons") is not a law firm and
  136. does not provide legal services or legal advice. Distribution of
  137. Creative Commons public licenses does not create a lawyer-client or
  138. other relationship. Creative Commons makes its licenses and related
  139. information available on an "as-is" basis. Creative Commons gives no
  140. warranties regarding its licenses, any material licensed under their
  141. terms and conditions, or any related information. Creative Commons
  142. disclaims all liability for damages resulting from their use to the
  143. fullest extent possible.
  144. Using Creative Commons Public Licenses
  145. Creative Commons public licenses provide a standard set of terms and
  146. conditions that creators and other rights holders may use to share
  147. original works of authorship and other material subject to copyright
  148. and certain other rights specified in the public license below. The
  149. following considerations are for informational purposes only, are not
  150. exhaustive, and do not form part of our licenses.
  151. Considerations for licensors: Our public licenses are
  152. intended for use by those authorized to give the public
  153. permission to use material in ways otherwise restricted by
  154. copyright and certain other rights. Our licenses are
  155. irrevocable. Licensors should read and understand the terms
  156. and conditions of the license they choose before applying it.
  157. Licensors should also secure all rights necessary before
  158. applying our licenses so that the public can reuse the
  159. material as expected. Licensors should clearly mark any
  160. material not subject to the license. This includes other CC-
  161. licensed material, or material used under an exception or
  162. limitation to copyright. More considerations for licensors:
  163. wiki.creativecommons.org/Considerations_for_licensors
  164. Considerations for the public: By using one of our public
  165. licenses, a licensor grants the public permission to use the
  166. licensed material under specified terms and conditions. If
  167. the licensor's permission is not necessary for any reason--for
  168. example, because of any applicable exception or limitation to
  169. copyright--then that use is not regulated by the license. Our
  170. licenses grant only permissions under copyright and certain
  171. other rights that a licensor has authority to grant. Use of
  172. the licensed material may still be restricted for other
  173. reasons, including because others have copyright or other
  174. rights in the material. A licensor may make special requests,
  175. such as asking that all changes be marked or described.
  176. Although not required by our licenses, you are encouraged to
  177. respect those requests where reasonable. More considerations
  178. for the public:
  179. wiki.creativecommons.org/Considerations_for_licensees
  180. =======================================================================
  181. Creative Commons Attribution-NoDerivatives 4.0 International Public
  182. License
  183. By exercising the Licensed Rights (defined below), You accept and agree
  184. to be bound by the terms and conditions of this Creative Commons
  185. Attribution-NoDerivatives 4.0 International Public License ("Public
  186. License"). To the extent this Public License may be interpreted as a
  187. contract, You are granted the Licensed Rights in consideration of Your
  188. acceptance of these terms and conditions, and the Licensor grants You
  189. such rights in consideration of benefits the Licensor receives from
  190. making the Licensed Material available under these terms and
  191. conditions.
  192. Section 1 -- Definitions.
  193. a. Adapted Material means material subject to Copyright and Similar
  194. Rights that is derived from or based upon the Licensed Material
  195. and in which the Licensed Material is translated, altered,
  196. arranged, transformed, or otherwise modified in a manner requiring
  197. permission under the Copyright and Similar Rights held by the
  198. Licensor. For purposes of this Public License, where the Licensed
  199. Material is a musical work, performance, or sound recording,
  200. Adapted Material is always produced where the Licensed Material is
  201. synched in timed relation with a moving image.
  202. b. Copyright and Similar Rights means copyright and/or similar rights
  203. closely related to copyright including, without limitation,
  204. performance, broadcast, sound recording, and Sui Generis Database
  205. Rights, without regard to how the rights are labeled or
  206. categorized. For purposes of this Public License, the rights
  207. specified in Section 2(b)(1)-(2) are not Copyright and Similar
  208. Rights.
  209. c. Effective Technological Measures means those measures that, in the
  210. absence of proper authority, may not be circumvented under laws
  211. fulfilling obligations under Article 11 of the WIPO Copyright
  212. Treaty adopted on December 20, 1996, and/or similar international
  213. agreements.
  214. d. Exceptions and Limitations means fair use, fair dealing, and/or
  215. any other exception or limitation to Copyright and Similar Rights
  216. that applies to Your use of the Licensed Material.
  217. e. Licensed Material means the artistic or literary work, database,
  218. or other material to which the Licensor applied this Public
  219. License.
  220. f. Licensed Rights means the rights granted to You subject to the
  221. terms and conditions of this Public License, which are limited to
  222. all Copyright and Similar Rights that apply to Your use of the
  223. Licensed Material and that the Licensor has authority to license.
  224. g. Licensor means the individual(s) or entity(ies) granting rights
  225. under this Public License.
  226. h. Share means to provide material to the public by any means or
  227. process that requires permission under the Licensed Rights, such
  228. as reproduction, public display, public performance, distribution,
  229. dissemination, communication, or importation, and to make material
  230. available to the public including in ways that members of the
  231. public may access the material from a place and at a time
  232. individually chosen by them.
  233. i. Sui Generis Database Rights means rights other than copyright
  234. resulting from Directive 96/9/EC of the European Parliament and of
  235. the Council of 11 March 1996 on the legal protection of databases,
  236. as amended and/or succeeded, as well as other essentially
  237. equivalent rights anywhere in the world.
  238. j. You means the individual or entity exercising the Licensed Rights
  239. under this Public License. Your has a corresponding meaning.
  240. Section 2 -- Scope.
  241. a. License grant.
  242. 1. Subject to the terms and conditions of this Public License,
  243. the Licensor hereby grants You a worldwide, royalty-free,
  244. non-sublicensable, non-exclusive, irrevocable license to
  245. exercise the Licensed Rights in the Licensed Material to:
  246. a. reproduce and Share the Licensed Material, in whole or
  247. in part; and
  248. b. produce and reproduce, but not Share, Adapted Material.
  249. 2. Exceptions and Limitations. For the avoidance of doubt, where
  250. Exceptions and Limitations apply to Your use, this Public
  251. License does not apply, and You do not need to comply with
  252. its terms and conditions.
  253. 3. Term. The term of this Public License is specified in Section
  254. 6(a).
  255. 4. Media and formats; technical modifications allowed. The
  256. Licensor authorizes You to exercise the Licensed Rights in
  257. all media and formats whether now known or hereafter created,
  258. and to make technical modifications necessary to do so. The
  259. Licensor waives and/or agrees not to assert any right or
  260. authority to forbid You from making technical modifications
  261. necessary to exercise the Licensed Rights, including
  262. technical modifications necessary to circumvent Effective
  263. Technological Measures. For purposes of this Public License,
  264. simply making modifications authorized by this Section 2(a)
  265. (4) never produces Adapted Material.
  266. 5. Downstream recipients.
  267. a. Offer from the Licensor -- Licensed Material. Every
  268. recipient of the Licensed Material automatically
  269. receives an offer from the Licensor to exercise the
  270. Licensed Rights under the terms and conditions of this
  271. Public License.
  272. b. No downstream restrictions. You may not offer or impose
  273. any additional or different terms or conditions on, or
  274. apply any Effective Technological Measures to, the
  275. Licensed Material if doing so restricts exercise of the
  276. Licensed Rights by any recipient of the Licensed
  277. Material.
  278. 6. No endorsement. Nothing in this Public License constitutes or
  279. may be construed as permission to assert or imply that You
  280. are, or that Your use of the Licensed Material is, connected
  281. with, or sponsored, endorsed, or granted official status by,
  282. the Licensor or others designated to receive attribution as
  283. provided in Section 3(a)(1)(A)(i).
  284. b. Other rights.
  285. 1. Moral rights, such as the right of integrity, are not
  286. licensed under this Public License, nor are publicity,
  287. privacy, and/or other similar personality rights; however, to
  288. the extent possible, the Licensor waives and/or agrees not to
  289. assert any such rights held by the Licensor to the limited
  290. extent necessary to allow You to exercise the Licensed
  291. Rights, but not otherwise.
  292. 2. Patent and trademark rights are not licensed under this
  293. Public License.
  294. 3. To the extent possible, the Licensor waives any right to
  295. collect royalties from You for the exercise of the Licensed
  296. Rights, whether directly or through a collecting society
  297. under any voluntary or waivable statutory or compulsory
  298. licensing scheme. In all other cases the Licensor expressly
  299. reserves any right to collect such royalties.
  300. Section 3 -- License Conditions.
  301. Your exercise of the Licensed Rights is expressly made subject to the
  302. following conditions.
  303. a. Attribution.
  304. 1. If You Share the Licensed Material, You must:
  305. a. retain the following if it is supplied by the Licensor
  306. with the Licensed Material:
  307. i. identification of the creator(s) of the Licensed
  308. Material and any others designated to receive
  309. attribution, in any reasonable manner requested by
  310. the Licensor (including by pseudonym if
  311. designated);
  312. ii. a copyright notice;
  313. iii. a notice that refers to this Public License;
  314. iv. a notice that refers to the disclaimer of
  315. warranties;
  316. v. a URI or hyperlink to the Licensed Material to the
  317. extent reasonably practicable;
  318. b. indicate if You modified the Licensed Material and
  319. retain an indication of any previous modifications; and
  320. c. indicate the Licensed Material is licensed under this
  321. Public License, and include the text of, or the URI or
  322. hyperlink to, this Public License.
  323. For the avoidance of doubt, You do not have permission under
  324. this Public License to Share Adapted Material.
  325. 2. You may satisfy the conditions in Section 3(a)(1) in any
  326. reasonable manner based on the medium, means, and context in
  327. which You Share the Licensed Material. For example, it may be
  328. reasonable to satisfy the conditions by providing a URI or
  329. hyperlink to a resource that includes the required
  330. information.
  331. 3. If requested by the Licensor, You must remove any of the
  332. information required by Section 3(a)(1)(A) to the extent
  333. reasonably practicable.
  334. Section 4 -- Sui Generis Database Rights.
  335. Where the Licensed Rights include Sui Generis Database Rights that
  336. apply to Your use of the Licensed Material:
  337. a. for the avoidance of doubt, Section 2(a)(1) grants You the right
  338. to extract, reuse, reproduce, and Share all or a substantial
  339. portion of the contents of the database, provided You do not Share
  340. Adapted Material;
  341. b. if You include all or a substantial portion of the database
  342. contents in a database in which You have Sui Generis Database
  343. Rights, then the database in which You have Sui Generis Database
  344. Rights (but not its individual contents) is Adapted Material; and
  345. c. You must comply with the conditions in Section 3(a) if You Share
  346. all or a substantial portion of the contents of the database.
  347. For the avoidance of doubt, this Section 4 supplements and does not
  348. replace Your obligations under this Public License where the Licensed
  349. Rights include other Copyright and Similar Rights.
  350. Section 5 -- Disclaimer of Warranties and Limitation of Liability.
  351. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
  352. EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
  353. AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
  354. ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
  355. IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
  356. WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
  357. PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
  358. ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
  359. KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
  360. ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
  361. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
  362. TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
  363. NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
  364. INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
  365. COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
  366. USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
  367. ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
  368. DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
  369. IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
  370. c. The disclaimer of warranties and limitation of liability provided
  371. above shall be interpreted in a manner that, to the extent
  372. possible, most closely approximates an absolute disclaimer and
  373. waiver of all liability.
  374. Section 6 -- Term and Termination.
  375. a. This Public License applies for the term of the Copyright and
  376. Similar Rights licensed here. However, if You fail to comply with
  377. this Public License, then Your rights under this Public License
  378. terminate automatically.
  379. b. Where Your right to use the Licensed Material has terminated under
  380. Section 6(a), it reinstates:
  381. 1. automatically as of the date the violation is cured, provided
  382. it is cured within 30 days of Your discovery of the
  383. violation; or
  384. 2. upon express reinstatement by the Licensor.
  385. For the avoidance of doubt, this Section 6(b) does not affect any
  386. right the Licensor may have to seek remedies for Your violations
  387. of this Public License.
  388. c. For the avoidance of doubt, the Licensor may also offer the
  389. Licensed Material under separate terms or conditions or stop
  390. distributing the Licensed Material at any time; however, doing so
  391. will not terminate this Public License.
  392. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
  393. License.
  394. Section 7 -- Other Terms and Conditions.
  395. a. The Licensor shall not be bound by any additional or different
  396. terms or conditions communicated by You unless expressly agreed.
  397. b. Any arrangements, understandings, or agreements regarding the
  398. Licensed Material not stated herein are separate from and
  399. independent of the terms and conditions of this Public License.
  400. Section 8 -- Interpretation.
  401. a. For the avoidance of doubt, this Public License does not, and
  402. shall not be interpreted to, reduce, limit, restrict, or impose
  403. conditions on any use of the Licensed Material that could lawfully
  404. be made without permission under this Public License.
  405. b. To the extent possible, if any provision of this Public License is
  406. deemed unenforceable, it shall be automatically reformed to the
  407. minimum extent necessary to make it enforceable. If the provision
  408. cannot be reformed, it shall be severed from this Public License
  409. without affecting the enforceability of the remaining terms and
  410. conditions.
  411. c. No term or condition of this Public License will be waived and no
  412. failure to comply consented to unless expressly agreed to by the
  413. Licensor.
  414. d. Nothing in this Public License constitutes or may be interpreted
  415. as a limitation upon, or waiver of, any privileges and immunities
  416. that apply to the Licensor or You, including from the legal
  417. processes of any jurisdiction or authority.
  418. =======================================================================
  419. Creative Commons is not a party to its public
  420. licenses. Notwithstanding, Creative Commons may elect to apply one of
  421. its public licenses to material it publishes and in those instances
  422. will be considered the “Licensor.” The text of the Creative Commons
  423. public licenses is dedicated to the public domain under the CC0 Public
  424. Domain Dedication. Except for the limited purpose of indicating that
  425. material is shared under a Creative Commons public license or as
  426. otherwise permitted by the Creative Commons policies published at
  427. creativecommons.org/policies, Creative Commons does not authorize the
  428. use of the trademark "Creative Commons" or any other trademark or logo
  429. of Creative Commons without its prior written consent including,
  430. without limitation, in connection with any unauthorized modifications
  431. to any of its public licenses or any other arrangements,
  432. understandings, or agreements concerning use of licensed material. For
  433. the avoidance of doubt, this paragraph does not form part of the
  434. public licenses.
  435. Creative Commons may be contacted at creativecommons.org.
  436. */
  437. (function() {
  438. 'use strict';
  439. if(typeof window.unsafeWindow === 'undefined') {
  440. window.unsafeWindow = window;
  441. }
  442. if(unsafeWindow.___cbEnhancer___) {
  443. return;
  444. }
  445. unsafeWindow.___cbEnhancer___ = true;
  446. function addStyle(style) {
  447. if(typeof GM_addStyle !== 'undefined') {
  448. return GM_addStyle(style);
  449. }
  450. let styleEl = document.createElement('style');
  451. styleEl.textContent = style;
  452. document.head.appendChild(styleEl);
  453. }
  454. function addElement(parent_node, tag_name, attributes) {
  455. if(typeof GM_addElement !== 'undefined') {
  456. return GM_addElement(parent_node, tag_name, attributes)
  457. }
  458. let id = Date.now();
  459. let msgData = {
  460. 'msg-name': 'cb-enh-add-element',
  461. 'data': {
  462. 'parent-node': id,
  463. 'tag-name': tag_name,
  464. 'attributes': attributes
  465. }
  466. };
  467. $(parent_node).addClass('cb-enh-add-element-id-' + id);
  468. window.postMessage(JSON.stringify(msgData), '*');
  469. }
  470. let xhrTasks = [];
  471. function xmlhttpRequest(details) {
  472. if(typeof GM_xmlhttpRequest !== 'undefined') {
  473. return GM_xmlhttpRequest(details);
  474. }
  475. let id = xhrTasks.length;
  476. xhrTasks[id] = details;
  477. let msgData = {
  478. 'msg-name': 'cb-enh-xhr',
  479. 'data': {
  480. 'xhr-id': id,
  481. 'details': details
  482. }
  483. };
  484. window.postMessage(JSON.stringify(msgData), '*');
  485. }
  486. window.addEventListener('message', function(e) {
  487. let msgData;
  488. try {
  489. msgData = JSON.parse(e.data);
  490. }
  491. catch(e) {
  492. return;
  493. }
  494. if(!('msg-name' in msgData) || msgData['msg-name'] != 'cb-enh-xhr-event') {
  495. return;
  496. }
  497. if(!('data' in msgData) || !('data' in msgData['data'])) {
  498. return;
  499. }
  500. let id = msgData['data']['xhr-id'];
  501. if(!(id in xhrTasks)) {
  502. return;
  503. }
  504. let event_name = msgData['data']['event-name'];
  505. let event = msgData['data']['event'];
  506. event.responseText = msgData['data']['responseText'];
  507. if(!xhrTasks[id][event_name]) {
  508. return;
  509. }
  510. xhrTasks[id][event_name](event);
  511. });
  512. function registerMenuCommand(name, callback, accessKey) {
  513. if(typeof GM_registerMenuCommand !== 'undefined') {
  514. return GM_registerMenuCommand(name, callback, accessKey);
  515. }
  516. }
  517. function unregisterMenuCommand(menuCmdId) {
  518. if(typeof GM_unregisterMenuCommand !== 'undefined') {
  519. return GM_unregisterMenuCommand(menuCmdId);
  520. }
  521. }
  522. function setClipboard(data, info) {
  523. if(typeof GM_setClipboard !== 'undefined') {
  524. return GM_setClipboard(data, info);
  525. }
  526. }
  527. let gVersion = '5.1.3';
  528. let intvWaitBody = null;
  529. let intvWaitVideo = null;
  530. let intvUpdateAvatarInPrivBoard = null;
  531. let intvUpdateFollowedList = null;
  532. let gIntvCheckIfRoomIsOnline = null;
  533. let gSettings = {};
  534. let gLocales = {};
  535. let currentHoverInterval = null;
  536. let lastLoadedThumbReqTime = 0;
  537. let gCurrentBroadcaster = null;
  538. let gCurrentRoomIsInaccessible = false;
  539. let gVideoControlsModalShown = false;
  540. let gCapturingScreenshot = false;
  541. let gRecording = false;
  542. let gRecordingStream = null;
  543. let gRecordingCanceledByUser = false;
  544. let gIsInMultiView = false;
  545. let gMoreSVG = '<svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" width="20"><path d="M479.788-192Q450-192 429-213.212q-21-21.213-21-51Q408-294 429.212-315q21.213-21 51-21Q510-336 531-314.788q21 21.213 21 51Q552-234 530.788-213q-21.213 21-51 21Zm0-216Q450-408 429-429.212q-21-21.213-21-51Q408-510 429.212-531q21.213-21 51-21Q510-552 531-530.788q21 21.213 21 51Q552-450 530.788-429q-21.213 21-51 21Zm0-216Q450-624 429-645.212q-21-21.213-21-51Q408-726 429.212-747q21.213-21 51-21Q510-768 531-746.788q21 21.213 21 51Q552-666 530.788-645q-21.213 21-51 21Z"/></svg>';
  546. function getCookie(name) {
  547. let nameEQ = encodeURIComponent(name) + "=";
  548. let ca = document.cookie.split(';');
  549. for(let i = 0; i < ca.length; i++) {
  550. let c = ca[i];
  551. while(c.charAt(0) === ' ') {
  552. c = c.substring(1, c.length);
  553. }
  554. if(c.indexOf(nameEQ) === 0) {
  555. return decodeURIComponent(c.substring(nameEQ.length, c.length));
  556. }
  557. }
  558. return null;
  559. }
  560. function doNeedDarkMode() {
  561. return getCookie('theme_name') === null && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  562. }
  563. function enableDarkMode() {
  564. if(document.body && doNeedDarkMode()) {
  565. $('body').addClass('darkmode');
  566. document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
  567. document.cookie = 'theme_name=darkmode; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
  568. }
  569. }
  570. if(doNeedDarkMode()) {
  571. if(document.body) {
  572. enableDarkMode();
  573. }
  574. else {
  575. intvWaitBody = setInterval(function() {
  576. if(document.body) {
  577. enableDarkMode();
  578. clearIntervalEx(intvWaitBody);
  579. }
  580. }, 10);
  581. }
  582. }
  583. function isMultiViewPage() {
  584. return window.location.pathname === '/multicam/';
  585. }
  586. if(isMultiViewPage()) {
  587. addStyle(`.content, div[data-testid="theatermode-root"] {display: none !important;}`);
  588. }
  589. let multiGridColumnCount = 60;
  590. let style = `.photoVideoDetailSection img {filter: unset !important;}.userUpload div {background: none !important;}.psContainer .lockOverlayBg, .smContainer .lockOverlayBg {display: none !important;}a[data-testid="photo-video-item"] > img[data-testid="lock-icon"] {display: none !important;}a[data-testid="photo-video-item"] > div:not(.link) {background: none !important;}.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;}tr:not(.smContainer) .contentText .previewBorder, div[data-testid="photoVideoPreview"] {width: 190px !important;height: 135px !important;}div[data-testid="photoVideoPreview"] .previewBorder {width: 100%;height: 100%;}a[data-testid="photo-video-item"] .tokenText {top: unset !important;bottom: 11px !important;right: -5px !important;}.userUpload img[src$="/tsdefaultassets/video.svg"] {top: 4px !important;right: 4px !important;}.userUpload img[src$="/tsdefaultassets/no-audio.svg"] {top: 4px !important;right: 26px !important;}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;}tr:not(.smContainer):not(.psContainer) .contentText * {background: unset !important;}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: 15;line-height: 22px;box-shadow: rgba(0, 0, 0, 0.08) 0px 4px 16px;background-color: #fdfdfd;border: 1px solid #acacac;}.darkmode .cb-enh-tab-bar-modal {background-color: #19222c;border-color: #003061;color: #f0f0f0;}.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;}.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;}.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;}#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;}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;}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;}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;}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;}#cb-enh-theater-controls-area {position: absolute;width: 100%;heigth: 50px;}.vjs-menu-button-popup .vjs-menu .vjs-menu-content {max-height: 20em !important;}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;}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;}.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;}#cb-enh-add-multi-link {text-decoration: none;}.cb-enh-add-cam-icon, .cb-enh-room-more-icon {float: right;cursor: pointer;}.cb-enh-room-more-icon {margin-right: 10px;}.cb-enh-add-cam-icon {margin-right: 2px;}.sub-info li.cams {width: 100% !important;}.cb-enh-room-more-icon {top: -1px;position: relative;}.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,.darkmode .cb-enh-room-more-icon svg path {fill: #fbfbfb;}.cb-enh-big-window {position: fixed;z-index: 99999999;top: 0;left: 0;width: 100%;display: flex;justify-content: center;}.cb-enh-big-window a {text-decoration: underline;color: black;}.darkmode .cb-enh-big-window a {color: white;}.cb-enh-big-window-close-icon {cursor: pointer;position: absolute;top: 4px;right: 4px;}.cb-enh-big-window-close-icon svg path {fill: #e80000;}.cb-enh-big-window-inner {position: fixed;width: 100%;max-width: 800px;height: 400px;border-radius: 4px;border: 1px solid #acacac;background-color: #fbfbfb;padding: 8px;top: 25%;}.darkmode .cb-enh-big-window-inner {border: 1px solid #121820;background-color: #17202a;color: #f7f7f7;}.cb-enh-big-window-content {overflow: auto;width: 100%;height: 87%;}.cb-enh-big-window-header {font-size: 22px;font-weight: bold;margin-top: 5px;margin-bottom: 15px;}.cb-enh-big-window-subheader {font-size: 18px;font-weight: bold;}#cb-enh-page-overlay {display: none;position: fixed;top: 0;left: 0;width: 100%;height: 100%;background-color: #0000008a;z-index: 99999996;}body > #base.cb-enh-base-blurred {-moz-filter: blur(10px);-ms-filter: blur(10px);-o-filter: blur(10px);filter: blur(10px);transform: translateZ(0);pointer-events: none;}html.cb-enh-html-noscroll {overflow: hidden;}#nav {overflow: visible !important;}#cb-enh-toggle-settings {text-transform: uppercase;}#cb-enh-settings-window {display: none;}.cb-enh-st-blocklist-username-remove-icon {display: inline-block;cursor: pointer;vertical-align: middle;width: 17px;height: 17px;}.cb-enh-st-blocklist-username-remove-icon svg {width: 100%;height: 100%;}.cb-enh-st-blocklist-username-remove-icon svg path {fill: #e80000;}.cb-enh-st-blocklist-username {height: 20px;}#cb-enh-more-rooms-size-selectors {display: none;position: relative;top: 24px;right: 20px;}#roomTabs:has(.MoreRooms[style*="display: block;"]) #cb-enh-more-rooms-size-selectors {display: block;}#cb-enh-notification {display: none;position: fixed;z-index: 99999995;bottom: 10px;width: 100%;justify-content: center;pointer-events: none;}#cb-enh-notification-inner {width: 100%;max-width: 800px;height: 35px;border-radius: 4px;border: 1px solid #acacac;background-color: #fbfbfb;padding: 8px;pointer-events: all;font-size: 16px;opacity: 0.95;}.darkmode #cb-enh-notification-inner {border: 1px solid #121820;background-color: #0d1318;color: #f7f7f7;}.cb-enh-room-more-menu {display: none; border-radius: 2px; position: absolute;width: 120px;height: 20px; font-size: 13px; border: 1px solid #acacac; background-color: #f0f1f1; color: black;z-index: 99999997;list-style: none;padding: 0;margin: 0;padding: 4px;}.darkmode .cb-enh-room-more-menu {border: 1px solid #2d3e50;background-color: #202b38;color: #fbfbfb;}.cb-enh-room-more-menu li {cursor: pointer;}.darkmode .roomElementAnchor.isHighlighted {background-color: #193655 !important;}.darkmode .roomElementAnchor:hover {color: #00b6ff !important;}.darkmode .roomCard .sub-info {color: #a1a1a1 !important;}#cb-enh-multi-support, .cb-enh-standalone-support-block {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, .darkmode .cb-enh-standalone-support-block {background-color: #012247;}#cb-enh-multi-support:hover, .cb-enh-standalone-support-block:hover {background-color: #003979;}#cb-enh-settings-support-block-outer {display: flex;position: fixed;z-index: 99999998;top: 0;left: 0;width: 100%;display: flex;justify-content: center;}#cb-enh-settings-support-block-outer.cb-enh-hidden {display: none;}#cb-enh-settings-support-block {width: 600px;position: fixed;bottom: 40px;z-index: 9999999;max-width: 800px;position: fixed;width: 100%;height: 70px;max-width: 800px;padding: 8px;}body.cb-enh-chat-hide-notices div[data-testid="chat-message"]:has(.roomNotice:not(.isTip)) {display: none !important;}body.cb-enh-chat-hide-tips div[data-testid="chat-message"]:has(.isTip) {display: none !important;}body.cb-enh-chat-hide-greys div[data-testid="chat-message"]:has(.defaultUser) {display: none !important;}/*.cb-enh-multi-cam-img-wrap {display: none;}.cb-enh-multi-cam-wrap2 {background-color: unset !important;}*/#cb-enh-multi-cams {display: grid;grid-template-columns: repeat(` + multiGridColumnCount + `, 1fr);grid-template-rows: repeat(` + multiGridColumnCount + `, 1px);}.langs {padding-bottom: 10px !important;}.cb-enh-footer {padding-bottom: 10px;}`;
  591. addStyle(style);
  592. 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" 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>`;
  593. let settingsContentHTML = `<div class="cb-enh-big-window" id="cb-enh-settings-window"><div class="cb-enh-big-window-inner"><div class="cb-enh-big-window-header">Chaturbate Enhancer <span class="ce-loc" data-ce-loc="settings">Settings</span></div><div class="cb-enh-big-window-close-icon" id="cb-enh-settings-window-close-icon">{{closeSVG}}</div><div class="cb-enh-big-window-content"><input type="checkbox" checked id="cb-enh-settings-input-show-logo"></input><label for="cb-enh-settings-input-show-logo" class="ce-loc" data-ce-loc="set_show_site_logo">Show site logo in video player</label><br><input type="checkbox" id="cb-enh-settings-auto-rules"></input><label for="cb-enh-settings-auto-rules" class="ce-loc" data-ce-loc="set_auto_rules">Automatically accept chat rules</label><br><input type="checkbox" id="cb-enh-settings-blur-mute"></input><label for="cb-enh-settings-blur-mute" class="ce-loc" data-ce-loc="set_blur_mute">Automatically mute inactive tab streams</label><br><input type="checkbox" id="cb-enh-settings-chat-hide-notices"></input><label for="cb-enh-settings-chat-hide-notices" class="ce-loc" data-ce-loc="chat_hide_notices">Chat: Hide notice messages</label><br><input type="checkbox" id="cb-enh-settings-chat-hide-tips"></input><label for="cb-enh-settings-chat-hide-tips" class="ce-loc" data-ce-loc="chat_hide_tips">Chat: Hide tips</label><br><input type="checkbox" id="cb-enh-settings-chat-hide-greys"></input><label for="cb-enh-settings-chat-hide-greys" class="ce-loc" data-ce-loc="chat_hide_greys">Chat: Hide messages from grey users</label><br><br><span class="ce-loc" data-ce-loc="set_show_these_genders">Show these genders in featured tab</span>:<input type="checkbox" checked class="cb-enh-settings-featuredg" id="cb-enh-settings-featuredg-f" data-gender="f"></input><label for="cb-enh-settings-featuredg-f" class="ce-loc" data-ce-loc="women">Women</label><input type="checkbox" checked class="cb-enh-settings-featuredg" id="cb-enh-settings-featuredg-m" data-gender="m"></input><label for="cb-enh-settings-featuredg-m" class="ce-loc" data-ce-loc="men">Men</label><input type="checkbox" checked class="cb-enh-settings-featuredg" id="cb-enh-settings-featuredg-c" data-gender="c"></input><label for="cb-enh-settings-featuredg-c" class="ce-loc" data-ce-loc="couples">Couples</label><input type="checkbox" checked class="cb-enh-settings-featuredg" id="cb-enh-settings-featuredg-t" data-gender="t"></input><label for="cb-enh-settings-featuredg-t" class="ce-loc" data-ce-loc="trans">Trans</label><br><br><div><span class="ce-loc cb-enh-big-window-subheader" data-ce-loc="hidden_rooms">Hidden rooms</span></div><div class="ce-loc" data-ce-loc="set_blocklist_info">Hidden rooms doesn't appear on the site. You can hide rooms by using dot menu on room list pages.</div><div class="ce-loc" data-ce-loc="set_blocklist_refresh_info">Refresh page to apply changes to this list.</div>{{settingsContentBlocklistUsernameHTML}}</div><div>Chaturbate™ Enhancer v` + gVersion + ` © <a href="https://improper.dev/" target="_blank" rel="noopener" referrerpolicy="origin">improper.dev</a></div></div></div>`;
  594. let acre6 = 'a1lOjBM8aQ0duanUhryu';
  595. let settingsContentExtraHTML = `<div id="cb-enh-page-overlay"></div><div id="cb-enh-settings-support-block-outer" class="cb-enh-hidden"><div class="cb-enh-standalone-support-block" id="cb-enh-settings-support-block"></div></div>`;
  596. let settingsContentBlocklistUsernameHTML = `<div class="cb-enh-st-blocklist-username"><div class="cb-enh-st-blocklist-username-remove-icon" data-username="{{username}}">{{closeSVG}}</div><span>{{username}}</span></div>`;
  597. function localizeStrings() {
  598. if(!gLocales) {
  599. return;
  600. }
  601. $('.ce-loc').each(function() {
  602. let v = $(this).data('ce-loc');
  603. if(gLocales[v]) {
  604. if($(this)[0].nodeName.toLowerCase() === 'input') {
  605. $(this).val(gLocales[v]);
  606. }
  607. else {
  608. $(this).text(gLocales[v]);
  609. }
  610. }
  611. });
  612. }
  613. function loadLocales(lang) {
  614. xmlhttpRequest({
  615. method: 'GET',
  616. url: 'https://cb-enh.improper.dev/locale/' + lang + '.json?key=' + acre6,
  617. timeout: 60*1*1000,
  618. onload: function(resp) {
  619. let data;
  620. try {
  621. data = JSON.parse(resp.responseText);
  622. }
  623. catch(SyntaxError) {
  624. return;
  625. }
  626. gLocales = data['locales'];
  627. localizeStrings();
  628. }
  629. });
  630. }
  631. function localizeStringsNextTick() {
  632. setTimeout(function() {
  633. localizeStrings();
  634. }, 1);
  635. }
  636. function getLocale(name, failsafe) {
  637. if(gLocales && gLocales[name]) {
  638. return gLocales[name];
  639. }
  640. if(failsafe) {
  641. return failsafe;
  642. }
  643. }
  644. function getSiteLang() {
  645. return $('html').attr('lang');
  646. }
  647. function isFirefox() {
  648. return navigator.userAgent.toLowerCase().indexOf('firefox') !== -1;
  649. }
  650. let lang = getSiteLang();
  651. if(lang !== 'en') {
  652. loadLocales(lang);
  653. }
  654. let gBlocklistRooms = [];
  655. let gHiddenGenders = [];
  656. let gHasHas = true;
  657. function blocklistLoad() {
  658. if(!localStorage) {
  659. return;
  660. }
  661. let ldata = blocklistLoadData();
  662. gBlocklistRooms = ldata['list'];
  663. gBlocklistRooms.forEach(function(username) {
  664. blocklistApply(username);
  665. });
  666. }
  667. function blocklistLoadData() {
  668. if(!localStorage) {
  669. return {
  670. 'list': []
  671. }
  672. }
  673. let ldata = localStorage.getItem('cb-enh-blocklist');
  674. if(ldata === null) {
  675. return {
  676. 'list': []
  677. }
  678. }
  679. try {
  680. ldata = JSON.parse(ldata);
  681. }
  682. catch(SyntaxError) {
  683. return {
  684. 'list': []
  685. }
  686. }
  687. if(!('list' in ldata)) {
  688. return {
  689. 'list': []
  690. }
  691. }
  692. return ldata;
  693. }
  694. function blocklistApply(username) {
  695. if(gHasHas) {
  696. let blocklistStyle = `.roomCard:has(a[data-room="` + username + `"]) {display: none !important;}`;
  697. addStyle(blocklistStyle);
  698. return;
  699. }
  700. $('.roomCard > a[data-room="' + username + '"]').parent().hide();
  701. }
  702. blocklistLoad();
  703. function blocklistAdd(username) {
  704. if(gBlocklistRooms.includes(username)) {
  705. return;
  706. }
  707. blocklistApply(username);
  708. gBlocklistRooms.push(username);
  709. let nMsg = getLocale('have_hidden', 'Hidden') + ' ' + username + '. ' + getLocale('bl_use_set', 'Use Chaturbate Enhancer settings to manage hidden rooms.');
  710. showNotification(nMsg);
  711. if(!localStorage) {
  712. return;
  713. }
  714. let ldata = blocklistLoadData();
  715. if(!ldata['list'].includes(username)) {
  716. ldata['list'].push(username);
  717. localStorage.setItem('cb-enh-blocklist', JSON.stringify(ldata));
  718. }
  719. }
  720. function blocklistRemove(username) {
  721. if(!gBlocklistRooms.includes(username)) {
  722. return;
  723. }
  724. let index = gBlocklistRooms.indexOf(username);
  725. if(index > -1) {
  726. gBlocklistRooms.splice(index, 1);
  727. }
  728. if(!localStorage) {
  729. return;
  730. }
  731. let ldata = {
  732. 'list': gBlocklistRooms
  733. };
  734. localStorage.setItem('cb-enh-blocklist', JSON.stringify(ldata));
  735. }
  736. function blocklistNotHasUpdate() {
  737. gBlocklistRooms.forEach(function(username) {
  738. blocklistApply(username);
  739. });
  740. if(gHiddenGenders) {
  741. gHiddenGenders.forEach(function(v) {
  742. blocklistGenderApply(v);
  743. });
  744. }
  745. }
  746. function setChatNoticesHidden(hidden) {
  747. $(document.body).toggleClass('cb-enh-chat-hide-notices', hidden === true);
  748. }
  749. function setChatTipsHidden(hidden) {
  750. $(document.body).toggleClass('cb-enh-chat-hide-tips', hidden === true);
  751. }
  752. function setChatGreysHidden(hidden) {
  753. $(document.body).toggleClass('cb-enh-chat-hide-greys', hidden === true);
  754. }
  755. let gMoreMenuHTML = `<ul id="cb-enh-room-more-menu" class="cb-enh-room-more-menu"><li id="cb-enh-more-menu-hide" class="ce-loc" data-ce-loc="hide_room">Hide room</a></ul>`;
  756. function setRoomMoreMenuVisible(visible, x, y) {
  757. let $menu = $('#cb-enh-room-more-menu');
  758. if(!visible) {
  759. $menu.hide();
  760. }
  761. else {
  762. $menu.show();
  763. $menu.css('left', x + 'px');
  764. $menu.css('top', y + 'px');
  765. }
  766. gRoomMoreMenuVisible = visible;
  767. }
  768. let regRedirPreventClick = false;
  769. $(document).ready(function() {
  770. enableDarkMode();
  771. clearIntervalEx(intvWaitBody);
  772. if(getCookie('theme_name') === 'darkmode') {
  773. let missingDarkMode = [
  774. '/my_collection/',
  775. '/fanclub/join/',
  776. '/supporter/upgrade/',
  777. '/terms/',
  778. '/privacy/',
  779. '/2257/',
  780. '/law_enforcement/',
  781. '/billingsupport/'
  782. ];
  783. missingDarkMode.forEach(function(v) {
  784. if(window.location.pathname.startsWith(v)) {
  785. $(document.body).addClass('darkmode');
  786. return false;
  787. }
  788. return true;
  789. });
  790. }
  791. if(!gHasHas && (gBlocklistRooms.length > 0 || gHiddenGenders.length > 0)) {
  792. blocklistLoad();
  793. setInterval(blocklistNotHasUpdate, 200);
  794. }
  795. if(getSetting('reg-redir') === 2) {
  796. setSetting('reg-redir', null);
  797. setSetting('reg-redir-room', null);
  798. $('.logo-zone a').click();
  799. regRedirPreventClick = true;
  800. }
  801. if(isMultiViewPage()) {
  802. initMultiView();
  803. }
  804. else if(window.location.pathname.startsWith('/roomlogin/')) {
  805. enhancePasswordedRoom();
  806. }
  807. else if('initialRoomDossier' in unsafeWindow) {
  808. enhanceRoom();
  809. }
  810. if(window.location.pathname.startsWith('/followed-cams/')) {
  811. enhanceFollowedList();
  812. }
  813. initAnimatedThumbs();
  814. initGridSizeSelector();
  815. initMultiViewUINavigation();
  816. let settingsHTML = settingsContentHTML.replace('{{closeSVG}}', closeSVG);
  817. let $settings = $(settingsHTML);
  818. $settings.appendTo('body');
  819. $(settingsContentExtraHTML).appendTo('body');
  820. adra($('.cb-enh-standalone-support-block-outer'));
  821. adra($('.cb-enh-standalone-support-block'));
  822. $(document).on("click", "#cb-enh-settings-input-show-logo", function() {
  823. let show = $(this)[0].checked;
  824. $("#VideoPanel .cbLogo").toggle(show);
  825. setSetting('hide-vid-logo', !show);
  826. $('#cb-enh-video-controls-modal-show-logo')[0].checked = show;
  827. });
  828. $(document).on("click", "#cb-enh-settings-auto-rules", function() {
  829. let enable = $(this)[0].checked;
  830. setSetting('auto-chat-rules', enable);
  831. });
  832. $(document).on("click", "#cb-enh-settings-blur-mute", function() {
  833. let enable = $(this)[0].checked;
  834. setSetting('blur-mute', enable);
  835. });
  836. $(document).on("click", "#cb-enh-more-menu-hide", function() {
  837. let username = $('#cb-enh-room-more-menu').data('username');
  838. blocklistAdd(username);
  839. });
  840. $(document).on("click", "#cb-enh-settings-chat-hide-notices", function() {
  841. let enable = $(this)[0].checked;
  842. setSetting('chat-hide-notices', enable);
  843. setChatNoticesHidden(enable);
  844. });
  845. $(document).on("click", "#cb-enh-settings-chat-hide-tips", function() {
  846. let enable = $(this)[0].checked;
  847. setSetting('chat-hide-tips', enable);
  848. setChatTipsHidden(enable);
  849. });
  850. $(document).on("click", "#cb-enh-settings-chat-hide-greys", function() {
  851. let enable = $(this)[0].checked;
  852. setSetting('chat-hide-greys', enable);
  853. setChatGreysHidden(enable);
  854. });
  855. $(document).on('change', '.cb-enh-settings-featuredg', function() {
  856. let gender = $(this).data('gender');
  857. let show = $(this).is(":checked");
  858. if(!localStorage) {
  859. return;
  860. }
  861. let hiddenGenders = getSetting('featured-hidden-genders');
  862. if(!hiddenGenders) {
  863. hiddenGenders = [];
  864. }
  865. let pos = hiddenGenders.indexOf(gender);
  866. if(pos !== -1) {
  867. hiddenGenders.splice(pos, 1);
  868. }
  869. if(!show) {
  870. hiddenGenders.push(gender);
  871. }
  872. setSetting('featured-hidden-genders', hiddenGenders);
  873. });
  874. let userType = getUserType();
  875. let msg = getSupportMessage(userType);
  876. if(msg !== null) {
  877. $(document).on('click', '#cb-enh-settings-support-block', function() {
  878. loutrreg(gCurrentBroadcaster);
  879. });
  880. $('#cb-enh-settings-support-block').html(msg);
  881. }
  882. setChatNoticesHidden(getSetting('chat-hide-notices'));
  883. setChatTipsHidden(getSetting('chat-hide-tips'));
  884. setChatGreysHidden(getSetting('chat-hide-greys'));
  885. let $noti = $(notificationWindowHTML);
  886. $noti.appendTo('body');
  887. $(gMoreMenuHTML).appendTo('body');
  888. let $footerInfo = '<div class="cb-enh-footer">Chaturbate™ Enhancer v' + gVersion + ' © <a href="https://improper.dev/" target="_blank" rel="noopener" referrerpolicy="origin">improper.dev</a></div>';
  889. $($footerInfo).insertAfter($('.langs'));
  890. localizeStringsNextTick();
  891. });
  892. let gWasVideoMutedBeforeBlur = false;
  893. window.addEventListener('blur',
  894. function() {
  895. if(!getSetting('blur-mute')) {
  896. return;
  897. }
  898. if(gIsInMultiView) {
  899. $('video').each(function() {
  900. this.muted = true;
  901. });
  902. return;
  903. }
  904. let $vid = getVideo();
  905. if($vid.length === 0) {
  906. return;
  907. }
  908. if(document.pictureInPictureElement !== null) {
  909. return;
  910. }
  911. gWasVideoMutedBeforeBlur = $vid[0].muted;
  912. $vid[0].muted = true;
  913. }
  914. );
  915. window.addEventListener('focus',
  916. function() {
  917. if(!getSetting('blur-mute')) {
  918. return;
  919. }
  920. let $vid = getVideo();
  921. if($vid.length === 0) {
  922. return;
  923. }
  924. $vid[0].muted = gWasVideoMutedBeforeBlur;
  925. }
  926. );
  927. function updateSettingsWindow() {
  928. let $windowBlock = $('#cb-enh-settings-window');
  929. let settingsHTML = settingsContentHTML.replace('{{closeSVG}}', closeSVG);
  930. let settingsContentBlocklistUsernameHTML0 = settingsContentBlocklistUsernameHTML.replaceAll('{{closeSVG}}', closeSVG);
  931. let settingsContentBlocklistUsernameHTMLFinal = '';
  932. let blData = blocklistLoadData();
  933. blData['list'].forEach(function(username) {
  934. settingsContentBlocklistUsernameHTMLFinal += settingsContentBlocklistUsernameHTML0.replaceAll('{{username}}', username);
  935. });
  936. $windowBlock[0].outerHTML = settingsHTML.replace('{{settingsContentBlocklistUsernameHTML}}', settingsContentBlocklistUsernameHTMLFinal);
  937. $('#cb-enh-settings-input-show-logo')[0].checked = !getSetting('hide-vid-logo');
  938. $('#cb-enh-settings-auto-rules')[0].checked = getSetting('auto-chat-rules');
  939. $('#cb-enh-settings-blur-mute')[0].checked = getSetting('blur-mute');
  940. $('#cb-enh-settings-chat-hide-notices')[0].checked = getSetting('chat-hide-notices');
  941. $('#cb-enh-settings-chat-hide-tips')[0].checked = getSetting('chat-hide-tips');
  942. $('#cb-enh-settings-chat-hide-greys')[0].checked = getSetting('chat-hide-greys');
  943. let hiddenGenders = getSetting('featured-hidden-genders');
  944. if(hiddenGenders) {
  945. hiddenGenders.forEach(function(v) {
  946. $('#cb-enh-settings-featuredg-' + v)[0].checked = false;
  947. });
  948. }
  949. localizeStringsNextTick();
  950. }
  951. function setSettingsOpen(open) {
  952. if(open) {
  953. updateSettingsWindow();
  954. $('#cb-enh-settings-window').css('display', 'flex');
  955. }
  956. else {
  957. $('#cb-enh-settings-window').css('display', 'none');
  958. }
  959. let userType = getUserType();
  960. let showSupportMsg = open && (userType === 0 || userType === 1);
  961. $('#cb-enh-settings-support-block-outer').toggleClass('cb-enh-hidden', !showSupportMsg);
  962. $('#cb-enh-page-overlay').toggle(open);
  963. $('body > #base').toggleClass('cb-enh-base-blurred', open);
  964. $('html').toggleClass('cb-enh-html-noscroll', open);
  965. if(open) {
  966. hideNotification();
  967. }
  968. }
  969. function initAnimatedThumbs() {
  970. let $checkboxComponent = $("#animate_thumbnails_form .checkboxComponent");
  971. $('#animate_thumbnails_form label').removeAttr("style");
  972. $("#animate_thumbnails_form .disabledTooltipColor").remove();
  973. $checkboxComponent.removeClass("disabled");
  974. $checkboxComponent.css("cursor", "pointer");
  975. $("#animate_thumbnails_form input").removeAttr("disabled");
  976. $("#animate_thumbnails_form input").removeAttr("readonly");
  977. $("#id_animate_thumbnails").css("cursor", "inherit");
  978. let animateThumbnails = getSetting("animate_thumbnails");
  979. if(animateThumbnails === null) {
  980. animateThumbnails = true;
  981. setSetting("animate_thumbnails", animateThumbnails);
  982. }
  983. $checkboxComponent.toggleClass("checked", animateThumbnails);
  984. $(document).on("click", "#animate_thumbnails_form", function(e) {
  985. $checkboxComponent.toggleClass("checked");
  986. setSetting("animate_thumbnails", $checkboxComponent.hasClass("checked"));
  987. e.preventDefault();
  988. e.stopPropagation();
  989. });
  990. $(document).on('mouseenter', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
  991. e.preventDefault();
  992. e.stopImmediatePropagation();
  993. if(!getSetting("animate_thumbnails")) {
  994. return;
  995. }
  996. clearIntervalEx(currentHoverInterval);
  997. updateRoomThumb($(this));
  998. currentHoverInterval = setInterval(() => {
  999. updateRoomThumb($(this));
  1000. }, 100);
  1001. });
  1002. $(document).on('mouseleave', '.room_list_room img, .roomElement img, .roomCard img', function(e) {
  1003. e.preventDefault();
  1004. e.stopImmediatePropagation();
  1005. clearIntervalEx(currentHoverInterval);
  1006. });
  1007. }
  1008. function updateRoomThumb($el) {
  1009. $el[0].onload = null;
  1010. let uname = $el.parent().data('room');
  1011. let reqTime = Date.now();
  1012. let req = new XMLHttpRequest();
  1013. req.timeout = 2000;
  1014. req.responseType = 'arraybuffer';
  1015. req.addEventListener('load', function() {
  1016. if(reqTime < lastLoadedThumbReqTime) {
  1017. return;
  1018. }
  1019. lastLoadedThumbReqTime = reqTime;
  1020. $el.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(req.response))));
  1021. });
  1022. req.open('GET', 'https://thumb.live.mmcdn.com/minifwap/' + uname + '.jpg?' + Math.random());
  1023. req.send();
  1024. }
  1025. document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
  1026. document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
  1027. document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
  1028. document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/';
  1029. document.cookie = 'noads=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
  1030. document.cookie = 'agreeterms=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
  1031. document.cookie = 'fromaffiliate=1; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
  1032. document.cookie = 'affkey="eJyrViopylayUlBKzctQ0lFQSkxLA/HMiwsM03KTQCIFIL6RIYhZBGKCGCUgRnpRoQGIk5wLVuKXZBFZpVQLAEdlFCg="; expires=Sun, 1 Jan 9999 00:00:00 UTC; path=/; domain=.chaturbate.com';
  1033. function enhanceRoom(ajaxTransition=false) {
  1034. unregisterMenuCommand(getLocale('get_vsurl', 'Get video source URL'));
  1035. $('.cb-enh-row').remove();
  1036. clearIntervalEx(gIntvCheckIfRoomIsOnline);
  1037. if(!ajaxTransition) {
  1038. let cFunc = function() {
  1039. if(!gCurrentBroadcaster) {
  1040. return;
  1041. }
  1042. let furl = 'https://chaturbate.com/api/chatvideocontext/' + gCurrentBroadcaster + '/';
  1043. if(gCurrentRoomIsInaccessible) {
  1044. furl = 'https://cb-enh-api2.improper.dev/api/room/' + gCurrentBroadcaster + '?key=' + acre7;
  1045. }
  1046. xmlhttpRequest({
  1047. method: 'GET',
  1048. url: furl,
  1049. headers: {
  1050. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  1051. 'Referer': 'https://chaturbate.com/' + gCurrentBroadcaster + '/',
  1052. },
  1053. timeout: 60*1*1000,
  1054. onload: function(resp) {
  1055. let data;
  1056. try {
  1057. data = JSON.parse(resp.responseText);
  1058. }
  1059. catch(SyntaxError) {
  1060. return;
  1061. }
  1062. if(!('hls_source' in data) || data['hls_source'] === '') {
  1063. alert(getLocale('err_vurl', 'ERROR: No video URL.'));
  1064. return;
  1065. }
  1066. let srcurl = data['hls_source'];
  1067. let pos = srcurl.indexOf('/playlist.m3u8');
  1068. if(pos === -1) {
  1069. pos = srcurl.indexOf('/playlist_sfm4s.m3u8');
  1070. }
  1071. if(pos === -1) {
  1072. alert(getLocale('err_vurl', 'ERROR: No video URL.'));
  1073. return;
  1074. }
  1075. srcurl = fixCBHLSURL(srcurl);
  1076. srcurl = srcurl.slice(0, pos + '/playlist_sfm4s.m3u8'.length);
  1077. setClipboard(srcurl, 'text');
  1078. alert(srcurl + '\n\n(' + getLocale('copied_to_clipboard', 'copied to clipboard') + ')');
  1079. }
  1080. });
  1081. }
  1082. registerMenuCommand(getLocale('get_vsurl', 'Get video source URL'), cFunc, 'g');
  1083. if(getSetting('hide-vid-logo')) {
  1084. addStyle('#VideoPanel .cbLogo { display: none; }');
  1085. }
  1086. clearIntervalEx(intvWaitVideo);
  1087. intvWaitVideo = setInterval(function() {
  1088. let $vid = getVideo();
  1089. if($vid.length === 0) {
  1090. return;
  1091. }
  1092. clearIntervalEx(intvWaitVideo);
  1093. if(unsafeWindow.videoJsPlayer && typeof $vid[0].requestPictureInPicture !== 'undefined') {
  1094. let PictureInPictureToggle = videojs.getComponent('pictureInPictureToggle');
  1095. if(PictureInPictureToggle) {
  1096. let pictureInPictureToggle = new PictureInPictureToggle(unsafeWindow.videoJsPlayer, {});
  1097. unsafeWindow.videoJsPlayer.getChild('ControlBar').addChild(pictureInPictureToggle);
  1098. }
  1099. }
  1100. $vid[0].addEventListener('resize', function(e) {
  1101. $(".vjs-live-display").text('LIVE - ' + e.target.videoWidth + ' x ' + e.target.videoHeight);
  1102. });
  1103. }, 50);
  1104. initSupportInfo();
  1105. }
  1106. if(unsafeWindow.initialRoomDossier === '') {
  1107. enhanceInaccessibleRoom();
  1108. return;
  1109. }
  1110. let intv = setInterval(function() {
  1111. if($('video.vjs-tech').length === 0) {
  1112. return;
  1113. }
  1114. clearIntervalEx(intv);
  1115. let currentUsername = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
  1116. let pageTransitionIntv = setInterval(function() {
  1117. let uname = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
  1118. if(currentUsername != uname) {
  1119. clearIntervalEx(pageTransitionIntv);
  1120. enhanceRoom(true);
  1121. currentUsername = uname;
  1122. }
  1123. }, 25);
  1124. }, 25);
  1125. let userData;
  1126. let broadcasterName;
  1127. if(!ajaxTransition) {
  1128. userData = JSON.parse(unsafeWindow.initialRoomDossier);
  1129. broadcasterName = userData.broadcaster_username;
  1130. if(userData['room_status'] !== 'offline') {
  1131. initBelowVideoButtons();
  1132. }
  1133. if(userData['room_status'] === 'offline') {
  1134. gIntvCheckIfRoomIsOnline = setInterval(
  1135. function() {
  1136. $.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
  1137. if(data['room_status'] !== 'offline') {
  1138. clearIntervalEx(gIntvCheckIfRoomIsOnline);
  1139. window.location.reload();
  1140. return;
  1141. }
  1142. });
  1143. }, 2000);
  1144. }
  1145. }
  1146. else {
  1147. broadcasterName = $('a.nextCamBgColor')[0].getAttribute('href').slice(6, -1);
  1148. stopRecording();
  1149. }
  1150. gCurrentBroadcaster = broadcasterName;
  1151. gCurrentRoomIsInaccessible = false;
  1152. let lang = getSiteLang();
  1153. let intervalId = setInterval(() => {
  1154. let $table = $('.BioContents > div > table');
  1155. if($table.length === 0) {
  1156. return;
  1157. }
  1158. clearIntervalEx(intervalId);
  1159. let $offlineNotice = $('.offlineRoomNotice');
  1160. if($offlineNotice.length > 0) {
  1161. insertRoomAv($offlineNotice, broadcasterName);
  1162. fetchRoomSnapshot(broadcasterName, function(resp) {
  1163. let $img = $('<img>');
  1164. $img.addClass('cb-enh-offline-snapshot');
  1165. $img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
  1166. $offlineNotice.append($img);
  1167. });
  1168. }
  1169. if(userData) {
  1170. if('allow_private_shows' in userData && userData.allow_private_shows) {
  1171. let spy = '';
  1172. if(userData.spy_private_show_price !== null && userData.spy_private_show_price > 0) {
  1173. spy = userData.spy_private_show_price + ' tk/min';
  1174. }
  1175. else {
  1176. spy = getLocale('disabled', 'disabled');
  1177. }
  1178. addBioRow('Privates auto recording', true, userData.allow_show_recordings ? getLocale('yes', 'yes') : getLocale('no', 'no'));
  1179. if('fan_club_spy_private_show_price' in userData && userData.fan_club_spy_private_show_price !== null && userData.fan_club_spy_private_show_price !== userData.spy_private_show_price) {
  1180. addBioRow('Private spy price (fanclub)', true, userData.fan_club_spy_private_show_price + ' tk/min');
  1181. }
  1182. addBioRow('Private spy price', true, spy);
  1183. addBioRow('Minimum private', true, userData.private_min_minutes + ' ' + getLocale('minutes', 'minutes'));
  1184. addBioRow('Private show price', true, userData.private_show_price + ' tk/min');
  1185. }
  1186. else {
  1187. addBioRow('Private shows', true, getLocale('no', 'no'));
  1188. }
  1189. }
  1190. let $divSchedule = addBioRow('Schedule', false, '<div id="cb-enh-iframe"></div>');
  1191. if(userData && 'room_status' in userData && userData.room_status === 'offline') {
  1192. addBioRow('Last Subject', true, userData.room_title);
  1193. }
  1194. let $divRegion = addBioRow('Region', false, '<a href=""></a>');
  1195. let $divOnlineFor = addBioRow('Online For', false);
  1196. xmlhttpRequest({
  1197. method: 'GET',
  1198. url: 'https://cb-enh-api.improper.dev/api/room/' + broadcasterName + '?lang=' + lang + '&key=' + acre3,
  1199. timeout: 60*2*1000,
  1200. onload: function(resp) {
  1201. let data;
  1202. try {
  1203. data = JSON.parse(resp.responseText);
  1204. }
  1205. catch(SyntaxError) {
  1206. return;
  1207. }
  1208. if(data['region'] !== '') {
  1209. let href = '';
  1210. if(data['region_id'] == 0) {
  1211. href = '/asian-cams/';
  1212. }
  1213. else if(data['region_id'] == 1) {
  1214. href = '/euro-russian-cams/';
  1215. }
  1216. else if(data['region_id'] == 2) {
  1217. href = '/north-american-cams/';
  1218. }
  1219. else if(data['region_id'] == 3) {
  1220. href = '/south-american-cams/';
  1221. }
  1222. else if(data['region_id'] == 4) {
  1223. href = '/other-region-cams/';
  1224. }
  1225. let elA = $divRegion.children('.cb-enh-row-value').children('a')[0];
  1226. elA.innerHTML = data['region'];
  1227. elA.href = href;
  1228. $divRegion.show();
  1229. }
  1230. if(data['online_for'] && data['online_for'] !== '') {
  1231. $divOnlineFor.children('.cb-enh-row-value')[0].innerHTML = data['online_for'];
  1232. $divOnlineFor.show();
  1233. }
  1234. if(data['has_schedule']) {
  1235. let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
  1236. let iframeWrapper = document.getElementById('cb-enh-iframe');
  1237. addElement(iframeWrapper, 'iframe', {
  1238. src: 'https://cb-enh-api.improper.dev/embed/schedule/' + broadcasterName + '?dark=' + darkMode + '&lang=' + lang + '&key=' + acre3,
  1239. class: 'cb-enh-schedule-frame'
  1240. });
  1241. $divSchedule.show();
  1242. }
  1243. localizeStrings();
  1244. }
  1245. });
  1246. let $fanclubBtn = $('.fanclubButton');
  1247. if($fanclubBtn.length > 0) {
  1248. $.getJSON('https://chaturbate.com/api/biocontext/' + broadcasterName + '/', function(data) {
  1249. if(!('performer_has_fanclub' in data) || !('fan_club_cost' in data)) {
  1250. return;
  1251. }
  1252. if(!data['performer_has_fanclub'] || data['fan_club_cost'] === null || $fanclubBtn.length === 0) {
  1253. return;
  1254. }
  1255. let $a = $fanclubBtn.find('span[style]').eq(0);
  1256. if($a.length === 0) {
  1257. return;
  1258. }
  1259. $a.text( $a.text() + ' (' + data['fan_club_cost'] + '/m)' );
  1260. });
  1261. }
  1262. if(!userData) {
  1263. $.getJSON('https://chaturbate.com/api/chatvideocontext/' + broadcasterName + '/', function(data) {
  1264. if('allow_private_shows' in data && data.allow_private_shows) {
  1265. let spy = '';
  1266. if(data.spy_private_show_price !== null && data.spy_private_show_price > 0) {
  1267. spy = data.spy_private_show_price + ' tk/min';
  1268. }
  1269. else {
  1270. spy = getLocale('disabled', 'disabled');
  1271. }
  1272. addBioRow('Privates auto recording', true, data.allow_show_recordings ? getLocale('yes', 'yes') : getLocale('no', 'no'));
  1273. if('fan_club_spy_private_show_price' in data && data.fan_club_spy_private_show_price !== null && data.fan_club_spy_private_show_price !== data.spy_private_show_price) {
  1274. addBioRow('Private spy price (fanclub)', true, data.fan_club_spy_private_show_price + ' tk/min');
  1275. }
  1276. addBioRow('Private spy price', true, spy);
  1277. addBioRow('Minimum private', true, data.private_min_minutes + ' ' + getLocale('minutes', 'minutes'));
  1278. addBioRow('Private show price', true, data.private_show_price + ' tk/min');
  1279. }
  1280. else {
  1281. addBioRow('Private shows', true, getLocale('no', 'no'));
  1282. }
  1283. });
  1284. }
  1285. if(!ajaxTransition) {
  1286. initMultiViewUIRoom();
  1287. }
  1288. localizeStringsNextTick();
  1289. }, 500);
  1290. clearIntervalEx(intvUpdateAvatarInPrivBoard);
  1291. intvUpdateAvatarInPrivBoard = setInterval(() => {
  1292. let $el = $('#VideoPanel div[ts]').eq(0);
  1293. if($el.data('cb-enh-av')) {
  1294. return;
  1295. }
  1296. let $div = $el.find('div:first-child').eq(0);
  1297. if($div.length === 0) {
  1298. return;
  1299. }
  1300. $el.data('cb-enh-av', true);
  1301. let $avDiv = insertRoomAv($div, broadcasterName);
  1302. $avDiv.css('margin', '0 auto');
  1303. $avDiv.css('margin-bottom', '10px');
  1304. }, 500);
  1305. if(getSetting('auto-chat-rules')) {
  1306. setTimeout(() => {
  1307. let $rulesEl = $('#ChatTabContainer .rulesModal');
  1308. if($rulesEl.length === 0) {
  1309. return;
  1310. }
  1311. $('.acceptRulesButton').click();
  1312. }, 500);
  1313. }
  1314. }
  1315. function initBelowVideoButtons() {
  1316. let intvAddVideoControlsBtn = setInterval(() => {
  1317. if($('#satisfactionScore').length === 0) {
  1318. return;
  1319. }
  1320. clearIntervalEx(intvAddVideoControlsBtn);
  1321. let $videoControlsModal = $('<div id="cb-enh-video-controls-modal" class="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>');
  1322. $(".tabBar").prepend($videoControlsModal);
  1323. $("#cb-enh-video-controls-modal-content").html(videoControlsContentHTML);
  1324. localizeStringsNextTick();
  1325. $('<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");
  1326. $('<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");
  1327. $('<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");
  1328. }, 100);
  1329. $(document).on("click", "#cb-enh-video-controls-btn", function(e) {
  1330. e.preventDefault();
  1331. e.stopPropagation();
  1332. if(gVideoControlsModalShown) {
  1333. setVideoControlsVisible(false);
  1334. return;
  1335. }
  1336. setVideoControlsVisible(true);
  1337. $("#cb-enh-video-controls-modal-show-logo")[0].checked = !getSetting('hide-vid-logo');
  1338. let $btn = $(this);
  1339. let $modal = $("#cb-enh-video-controls-modal");
  1340. let off = $btn.offset();
  1341. off.top -= $btn.outerHeight() + $modal.outerHeight();
  1342. off.left -= $($modal).outerWidth() / 2;
  1343. $modal.offset(off);
  1344. let height = $modal.outerHeight();
  1345. let $arrow = $("#cb-enh-video-controls-modal .cb-enh-tab-bar-modal-arrow-down");
  1346. $arrow.offset({
  1347. left: $btn.offset().left + $btn.outerWidth() / 2 - $arrow.outerWidth() / 2,
  1348. top: off.top + height
  1349. });
  1350. });
  1351. $(window).click(function(e) {
  1352. if(gVideoControlsModalShown && $(e.target).closest('#cb-enh-video-controls-modal').length === 0) {
  1353. setVideoControlsVisible(false);
  1354. }
  1355. });
  1356. $(document).on("click", "#cb-enh-video-controls-modal-show-logo", function() {
  1357. let show = $(this)[0].checked;
  1358. $("#VideoPanel .cbLogo").toggle(show);
  1359. setSetting('hide-vid-logo', !show);
  1360. $('#cb-enh-settings-input-show-logo')[0].checked = show;
  1361. });
  1362. $(document).on("click", "#cb-enh-video-controls-modal-mirror-vid", function() {
  1363. $(".videoPlayerDiv video").toggleClass("cb-enh-video-mirrored", $(this)[0].checked);
  1364. });
  1365. $(document).on("click", "#cb-enh-video-controls-modal-invert-vid", function() {
  1366. $(".videoPlayerDiv video").toggleClass("cb-enh-video-inverted", $(this)[0].checked);
  1367. });
  1368. $(document).on("input", "#cb-enh-video-controls-modal-brightness", function() {
  1369. vidFilters[0] = "brightness(" + $(this).val() + "%)";
  1370. updateVideoFilters();
  1371. });
  1372. $(document).on("input", "#cb-enh-video-controls-modal-contrast", function() {
  1373. vidFilters[1] = "contrast(" + $(this).val() + "%)";
  1374. updateVideoFilters();
  1375. });
  1376. $(document).on("input", "#cb-enh-video-controls-modal-saturation", function() {
  1377. vidFilters[2] = "saturate(" + $(this).val() + "%)";
  1378. updateVideoFilters();
  1379. });
  1380. $(document).on("input", "#cb-enh-video-controls-modal-sepia", function() {
  1381. vidFilters[3] = "sepia(" + $(this).val() + "%)";
  1382. updateVideoFilters();
  1383. });
  1384. $(document).on("input", "#cb-enh-video-controls-modal-hue", function() {
  1385. vidFilters[4] = "hue-rotate(" + $(this).val() + "deg)";
  1386. updateVideoFilters();
  1387. });
  1388. $(document).on("input", "#cb-enh-video-controls-modal-blur", function() {
  1389. vidFilters[5] = "blur(" + $(this).val() + "px)";
  1390. updateVideoFilters();
  1391. });
  1392. $(document).on("click", "#cb-enh-video-controls-modal-reset", function() {
  1393. vidFilters = [];
  1394. updateVideoFilters();
  1395. $(".videoPlayerDiv video").removeClass("cb-enh-video-mirrored");
  1396. $(".videoPlayerDiv video").removeClass("cb-enh-video-inverted");
  1397. $("#cb-enh-video-controls-modal-mirror-vid")[0].checked = false;
  1398. $("#cb-enh-video-controls-modal-invert-vid")[0].checked = false;
  1399. $(".cb-enh-vid-control-slider").each(function() {
  1400. $(this).val($(this).attr("data-default"));
  1401. });
  1402. });
  1403. $(document).on("click", "#cb-enh-video-controls-screenshot", function(e) {
  1404. e.preventDefault();
  1405. e.stopPropagation();
  1406. captureScreenshot();
  1407. });
  1408. $(document).on("click", "#cb-enh-video-controls-record", function(e) {
  1409. e.preventDefault();
  1410. e.stopPropagation();
  1411. if(!gRecording) {
  1412. startRecording();
  1413. }
  1414. else {
  1415. stopRecording();
  1416. }
  1417. });
  1418. let insideChat = false;
  1419. document.addEventListener('mouseover',
  1420. function(e) {
  1421. if($('.draggableCanvasChatWindow').find(e.target).length > 0) {
  1422. insideChat = true;
  1423. }
  1424. }
  1425. );
  1426. document.addEventListener('mouseout',
  1427. function(e) {
  1428. let $input = $('.fullvideoInputFieldChat');
  1429. if(insideChat && $input.length > 0 && document.activeElement !== $input[0] && $('.draggableCanvasChatWindow > div[ts="n"]').find(e.target).length > 0) {
  1430. insideChat = false;
  1431. resetTheaterModeChatVisibilityState();
  1432. }
  1433. }
  1434. );
  1435. document.addEventListener('click',
  1436. function(e) {
  1437. let $chatBtn = $('#TheaterModePlayer #chat-btn');
  1438. if($chatBtn.length > 0 && e.target === $chatBtn[0]) {
  1439. resetTheaterModeChatVisibilityState();
  1440. }
  1441. }
  1442. , true);
  1443. }
  1444. function resetTheaterModeChatVisibilityState() {
  1445. setTimeout(
  1446. function() {
  1447. if($('#TheaterModeRoomContents').length > 0) {
  1448. $('#TheaterModeRoomContents')[0].click();
  1449. }
  1450. },
  1451. 1
  1452. );
  1453. }
  1454. function setVideoControlsVisible(visible) {
  1455. $("#cb-enh-video-controls-modal").toggle(visible);
  1456. gVideoControlsModalShown = visible;
  1457. localizeStringsNextTick()
  1458. }
  1459. function initSupportInfo() {
  1460. $(document).on('click', '.cb-enh-acc-info-inner', function() {
  1461. loutrreg(gCurrentBroadcaster);
  1462. });
  1463. let intvWaitBuyBox = setInterval(() => {
  1464. let $bBox = $('div[data-paction=CurrentShowBuyBox]').parent();
  1465. if($bBox.length === 0) {
  1466. return;
  1467. }
  1468. clearIntervalEx(intvWaitBuyBox);
  1469. if($('#cb-enh-acc-info').length > 0) {
  1470. return;
  1471. }
  1472. let userType = getUserType();
  1473. let msg = getSupportMessage(userType);
  1474. if(msg === null) {
  1475. return;
  1476. }
  1477. 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>`;
  1478. $bBox.append(bBoxHtml);
  1479. adra($('#cb-enh-acc-info'));
  1480. adra($('.cb-enh-acc-info-outer'));
  1481. adra($('.cb-enh-acc-info-inner'));
  1482. }, 50);
  1483. setInterval(function() {
  1484. if($('#VideoPanel').length === 0) {
  1485. return;
  1486. }
  1487. let $accInfo = $('#cb-enh-acc-info');
  1488. if($accInfo.length === 0) {
  1489. return;
  1490. }
  1491. if($('#VideoPanel').innerWidth() >= 1000) {
  1492. $accInfo.css('font-size', '15px');
  1493. $accInfo.css('width', '500px');
  1494. $accInfo.show();
  1495. return;
  1496. }
  1497. let $parent = $accInfo.parent();
  1498. let w1 = $parent.innerWidth();
  1499. let chw1 = $parent.children().eq(0).innerWidth();
  1500. let chw2 = $parent.children().eq(1).innerWidth();
  1501. let w2 = w1 - chw1 - chw2;
  1502. if(w2 <= 30) {
  1503. $accInfo.hide();
  1504. return;
  1505. }
  1506. $accInfo.css('width', (w2 - 10) + 'px');
  1507. $accInfo.css('font-size', '12px');
  1508. $accInfo.show();
  1509. }, 200);
  1510. }
  1511. function isLogged() {
  1512. return $('.user_information_header_username').length !== 0;
  1513. }
  1514. function getUserType() {
  1515. let userType = 0;
  1516. if(isLogged()) {
  1517. userType = 2;
  1518. let gaq = $("#gaq").html();
  1519. if(gaq.search(atob(rev(acre8))) !== -1 && gaq.search(atob(rev(acre1 + acre2))) === -1) {
  1520. userType = 1;
  1521. }
  1522. }
  1523. return userType;
  1524. }
  1525. function getSupportMessage(userType) {
  1526. if(userType === 0) {
  1527. 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 small commission of every token spend by you. It cost you nothing but it will significantly help further development of Chaturbate Enhancer. This message will disappear.<span>`;
  1528. }
  1529. else if(userType === 1) {
  1530. 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 small commission of every token spend by you. It cost you nothing but it will significantly help further development of Chaturbate Enhancer. This message will disappear.<span></div>`;
  1531. }
  1532. return null;
  1533. }
  1534. function loutrreg() {
  1535. setSetting('reg-redir', 1);
  1536. setSetting('reg-redir-room', gCurrentBroadcaster);
  1537. if(isLogged()) {
  1538. $('a[href="/auth/logout/"]').click();
  1539. $(".modalAlert .dialog .accept").click();
  1540. }
  1541. else {
  1542. regRedir(gCurrentBroadcaster);
  1543. }
  1544. }
  1545. function regRedir(room) {
  1546. let rurl = '/accounts/register/';
  1547. if(room) {
  1548. rurl = '/' + room + '/?join_overlay=1&disable_sound=1';
  1549. }
  1550. setSetting('reg-redir', 2);
  1551. setSetting('reg-redir-room', room);
  1552. window.location.href = rurl;
  1553. }
  1554. function getVideo() {
  1555. return $(".videoPlayerDiv video");
  1556. }
  1557. function isVideoPlaying(vid) {
  1558. return !vid.paused && !vid.ended && vid.readyState > 2;
  1559. }
  1560. function playIgnoreErrors(vid) {
  1561. vid.play().catch(() => {
  1562. () => {} });
  1563. }
  1564. function setSetting(name, value) {
  1565. gSettings[name] = value;
  1566. localStorage.setItem('cb-enh-settings', JSON.stringify(gSettings));
  1567. }
  1568. function getSetting(name, value) {
  1569. if(typeof gSettings[name] !== 'undefined') {
  1570. return gSettings[name];
  1571. }
  1572. return null;
  1573. }
  1574. function loadSettings() {
  1575. let settings = localStorage.getItem('cb-enh-settings');
  1576. if(settings === null) {
  1577. settings = {};
  1578. }
  1579. else {
  1580. settings = JSON.parse(settings);
  1581. }
  1582. gSettings = settings;
  1583. }
  1584. let vidFilters = [];
  1585. function updateVideoFilters() {
  1586. getVideo().css("filter", vidFilters.join(" "));
  1587. }
  1588. let acre1 = '=cieZhjYOdCI';
  1589. function enhanceInaccessibleRoom() {
  1590. let lang = getSiteLang();
  1591. let $baseRoomContentDiv = $("div.BaseRoomContents div")
  1592. if($baseRoomContentDiv.length === 0) {
  1593. return;
  1594. }
  1595. $baseRoomContentDiv = $baseRoomContentDiv.eq(0);
  1596. if($baseRoomContentDiv.text().indexOf("Access denied") !== 0) {
  1597. return;
  1598. }
  1599. $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>');
  1600. let $langForm = $("form[action='/set_language/'] input[name='next']");
  1601. if($langForm.length === 0) {
  1602. return;
  1603. }
  1604. let username = $("form[action='/set_language/'] input[name='next']")[0].value.slice(1, -1);
  1605. gCurrentBroadcaster = username;
  1606. gCurrentRoomIsInaccessible = true;
  1607. 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;}`);
  1608. let $upperHolder = $('<div></div>');
  1609. $baseRoomContentDiv.append($upperHolder);
  1610. let $videoHolder = $('<div></div>');
  1611. $baseRoomContentDiv.append($videoHolder);
  1612. let $upperHolder2 = $('<div></div>');
  1613. $baseRoomContentDiv.append($upperHolder2);
  1614. let $upperHolder3 = $('<div></div>');
  1615. $baseRoomContentDiv.append($upperHolder3);
  1616. let $infoHolder = $('<div></div>');
  1617. $baseRoomContentDiv.append($infoHolder);
  1618. let $infoHolder2 = $('<div></div>');
  1619. $baseRoomContentDiv.append($infoHolder2);
  1620. let $scheduleHolder = $('<div></div>');
  1621. $baseRoomContentDiv.append($scheduleHolder);
  1622. let isOnline = false;
  1623. xmlhttpRequest({
  1624. method: 'GET',
  1625. url: 'https://cb-enh-api2.improper.dev/api/room/' + username + '?key=' + acre7,
  1626. headers: {
  1627. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  1628. 'Referer': 'https://chaturbate.com/' + username + '/',
  1629. },
  1630. timeout: 60*1*1000,
  1631. onload: function(resp) {
  1632. let data;
  1633. try {
  1634. data = JSON.parse(resp.responseText);
  1635. }
  1636. catch(SyntaxError) {
  1637. return;
  1638. }
  1639. if($baseRoomContentDiv.length === 0) {
  1640. return;
  1641. }
  1642. let playVideo = true;
  1643. if(data['room_status'] !== 'public') {
  1644. let $avDiv = $("<div></div>");
  1645. $upperHolder.append($avDiv);
  1646. insertRoomAv($avDiv, username);
  1647. $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>');
  1648. playVideo = false;
  1649. }
  1650. else {
  1651. isOnline = true;
  1652. $("#cb-enh-inac-load-chat").show()
  1653. }
  1654. if(data['hls_source'] === '') {
  1655. playVideo = false;
  1656. }
  1657. if(data['room_title']) {
  1658. let $span;
  1659. if(data['room_status'] !== 'offline') {
  1660. $span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="subject">Subject</span>: <span></span></span>');
  1661. }
  1662. else {
  1663. $span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="last_subject">Last Subject</span>: <span></span></span>');
  1664. }
  1665. $span.find('span').eq(1).text(data['room_title']);
  1666. $upperHolder.append($span);
  1667. $upperHolder.append('<br><br>');
  1668. }
  1669. if(playVideo) {
  1670. 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://jpeg.live.mmcdn.com/stream?room=' + username + '&f=' + Math.random() + '"></video>');
  1671. $videoHolder.append($video);
  1672. let hls_source = fixCBHLSURL(data['hls_source']);
  1673. let hls = new Hls();
  1674. hls.loadSource(hls_source);
  1675. hls.attachMedia($video[0]);
  1676. }
  1677. if(data['age']) {
  1678. $upperHolder3.append('<br><br><span class="ce-loc ce-row-1" data-ce-loc="age">Age</span>: ' + data['age'] + '<br>');
  1679. }
  1680. if(data['broadcaster_gender']) {
  1681. $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>');
  1682. }
  1683. if(data['num_viewers']) {
  1684. if(data['room_status'] !== 'offline') {
  1685. $upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="viewers">Viewers</span>: ' + data['num_viewers'] + '<br>');
  1686. }
  1687. else {
  1688. $upperHolder3.append('<span class="ce-loc ce-row-1" data-ce-loc="last_viewers">Last Viewers</span>: ' + data['num_viewers'] + '<br>');
  1689. }
  1690. }
  1691. if(data['performer_has_fanclub']) {
  1692. $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>');
  1693. }
  1694. else {
  1695. $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>');
  1696. }
  1697. if('satisfaction_score' in data) {
  1698. let sc = data['satisfaction_score'];
  1699. if('percent' in sc && 'up_votes' in sc && 'down_votes' in sc) {
  1700. $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>');
  1701. }
  1702. }
  1703. localizeStrings();
  1704. },
  1705. onerror: function() {
  1706. $upperHolder.append('<br>' + getLocale('err_vid', 'ERROR: Unable to load video.'));
  1707. }
  1708. });
  1709. xmlhttpRequest({
  1710. method: 'GET',
  1711. url: 'https://cb-enh-api.improper.dev/api/room/' + username + '?lang=' + lang + '&key=' + acre3,
  1712. timeout: 60*2*1000,
  1713. onload: function(resp) {
  1714. let data;
  1715. try {
  1716. data = JSON.parse(resp.responseText);
  1717. }
  1718. catch(SyntaxError) {
  1719. return;
  1720. }
  1721. 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>');
  1722. $upperHolder2.append($loadChatHref);
  1723. if(isOnline) {
  1724. $loadChatHref.show();
  1725. }
  1726. $loadChatHref.on('click', function(e) {
  1727. $loadChatHref.hide();
  1728. e.preventDefault();
  1729. e.stopPropagation();
  1730. $videoHolder.empty();
  1731. addElement($videoHolder[0], 'iframe', {
  1732. src: 'https://cb-enh-api2.improper.dev/embed/room/' + username + '?key=' + acre7,
  1733. class: 'cb-enh-chat-frame'
  1734. });
  1735. });
  1736. if(data['region'] !== '') {
  1737. let href = '';
  1738. if(data['region_id'] == 0) {
  1739. href = '/asian-cams/';
  1740. }
  1741. else if(data['region_id'] == 1) {
  1742. href = '/euro-russian-cams/';
  1743. }
  1744. else if(data['region_id'] == 2) {
  1745. href = '/north-american-cams/';
  1746. }
  1747. else if(data['region_id'] == 3) {
  1748. href = '/south-american-cams/';
  1749. }
  1750. else if(data['region_id'] == 4) {
  1751. href = '/other-region-cams/';
  1752. }
  1753. $infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="region">Region</span>: <a href="' + href + '">' + data['region'] + '</a><br>');
  1754. }
  1755. if(data['online_for'] && data['online_for'] !== '') {
  1756. $infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="online_for">Online For</span>: ' + data['online_for'] + '<br>');
  1757. }
  1758. else if(data['last_online_f'] && data['last_online_f'] !== '') {
  1759. $infoHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="last_online">Last Online</span>: ' + data['last_online_f'] + '<br>');
  1760. }
  1761. let info = {
  1762. 'real_name': 'Real Name',
  1763. 'birthday': 'Birthday',
  1764. 'followers_f': 'Followers',
  1765. 'location': 'Location',
  1766. 'languages': 'Languages',
  1767. 'smoke_drink': 'Smoke / Drink',
  1768. 'body_type': 'Body Type',
  1769. 'body_decorations': 'Body Decorations',
  1770. };
  1771. Object.keys(info).forEach(function(k) {
  1772. let v = info[k];
  1773. if(data[k]) {
  1774. let $span = $('<span><span class="ce-loc ce-row-1" data-ce-loc="' + k + '">' + v + '</span>: <span></span></span>');
  1775. $span.find('span').eq(1).text(data[k]);
  1776. $infoHolder.append($span);
  1777. $infoHolder.append('<br>');
  1778. }
  1779. });
  1780. if(data['has_schedule']) {
  1781. $scheduleHolder.append('<span class="ce-loc ce-row-1" data-ce-loc="schedule">Schedule</span>: <br>');
  1782. let darkMode = $('body').hasClass('darkmode') ? 1 : 0;
  1783. addElement($scheduleHolder[0], 'iframe', {
  1784. src: 'https://cb-enh-api.improper.dev/embed/schedule/' + username + '?dark=' + darkMode + '&lang=' + lang + '&key=' + acre3,
  1785. class: 'cb-enh-schedule-frame'
  1786. });
  1787. }
  1788. localizeStrings();
  1789. }
  1790. });
  1791. }
  1792. function enhancePasswordedRoom() {
  1793. }
  1794. function addBioRow(name, visible = true, value = '') {
  1795. let loc = name.replaceAll(' ', '_').toLowerCase();
  1796. 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>');
  1797. let $psContainers = $('.BioContents > div > table > .psContainer');
  1798. let $smContainers = $('.BioContents > div > table > .smContainer');
  1799. if($psContainers.length > 0) {
  1800. $psContainers.last().after($el);
  1801. }
  1802. else if($smContainers.length > 0) {
  1803. $smContainers.last().after($el);
  1804. }
  1805. else {
  1806. $('.BioContents > div > table > tr').slice(-2).first().after($el);
  1807. }
  1808. return $el;
  1809. }
  1810. let acre2 = '642ZpFGctF2Y';
  1811. function insertRoomAv($div, username) {
  1812. let $avDiv = $('<div class="cb-enh-avatar"></div>');
  1813. $div.prepend($avDiv);
  1814. addElement($avDiv[0], 'img', {
  1815. src: 'https://cb-enh-api.improper.dev/assets/img/avatar.png?key=' + acre4,
  1816. alt: '',
  1817. onload: 'this.style.opacity=1'
  1818. });
  1819. addElement($avDiv[0], 'img', {
  1820. src: 'https://cb-enh-thumb.improper.dev/av/' + username + '.jpg?key=' + acre5,
  1821. alt: '',
  1822. onload: 'this.style.opacity=1'
  1823. });
  1824. return $avDiv;
  1825. }
  1826. function isCurrentlyUsingAnyTextInput() {
  1827. if(!document.activeElement) {
  1828. return false;
  1829. }
  1830. if( ['input', 'textarea', 'search', 'text'].includes(document.activeElement.type) ) {
  1831. return true;
  1832. }
  1833. if(document.activeElement.hasAttribute('contenteditable')) {
  1834. return true;
  1835. }
  1836. return false;
  1837. }
  1838. document.addEventListener("keydown",
  1839. function(e) {
  1840. if(`${e.code}` === 'KeyX' && e.ctrlKey) {
  1841. if(isCurrentlyUsingAnyTextInput()) {
  1842. return;
  1843. }
  1844. captureScreenshot();
  1845. }
  1846. }
  1847. );
  1848. function captureScreenshot() {
  1849. if(gCapturingScreenshot) {
  1850. return;
  1851. }
  1852. let $vid = getVideo();
  1853. if($vid.length === 0) {
  1854. return;
  1855. }
  1856. gCapturingScreenshot = true;
  1857. let canvas = captureVideoFrame($vid[0]);
  1858. gCapturingScreenshot = false;
  1859. if(!canvas) {
  1860. alert(getLocale('err_ss', 'ERROR: Failed to capture screenshot!'));
  1861. return;
  1862. }
  1863. let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
  1864. let link = document.createElement('a');
  1865. let date = new Date();
  1866. link.download = getFileName(username, '.png', date);
  1867. link.href = canvas.toDataURL();
  1868. link.click();
  1869. }
  1870. function captureVideoFrame(video) {
  1871. let canvas = document.createElement("canvas");
  1872. canvas.width = video.videoWidth;
  1873. canvas.height = video.videoHeight;
  1874. canvas.getContext("2d").drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
  1875. return canvas;
  1876. }
  1877. function startRecording() {
  1878. if(gRecording) {
  1879. return;
  1880. }
  1881. let $vid = getVideo();
  1882. if($vid.length === 0) {
  1883. alert(getLocale('err_no_rec_video', 'ERROR: There is no video to record!'));
  1884. return;
  1885. }
  1886. let lengthInMS = 1000*60*10;
  1887. let username = gCurrentBroadcaster ? gCurrentBroadcaster : 'unknown';
  1888. let stream = captureStream($vid[0]);
  1889. if(stream === 'err-no-func') {
  1890. errRecordingNotSupported();
  1891. return;
  1892. }
  1893. if(typeof MediaRecorder === 'undefined') {
  1894. errRecordingNotSupported();
  1895. return;
  1896. }
  1897. let mimeTypes = [
  1898. ['video/mp4', 'mp4'],
  1899. ['video/webm', 'webm'],
  1900. ];
  1901. let mimeType = null;
  1902. let fileExt = null;
  1903. for(let k in mimeTypes) {
  1904. let v = mimeTypes[k];
  1905. if(MediaRecorder.isTypeSupported(v[0])) {
  1906. mimeType = v[0];
  1907. fileExt = v[1];
  1908. break;
  1909. }
  1910. }
  1911. if(!mimeType) {
  1912. errRecordingNotSupported();
  1913. return;
  1914. }
  1915. playIgnoreErrors($vid[0]);
  1916. if($vid[0].muted) {
  1917. stream.getAudioTracks().forEach( t => stream.removeTrack( t ) );
  1918. }
  1919. gRecording = true;
  1920. gRecordingCanceledByUser = false;
  1921. $("#cb-enh-video-controls-record").find("span").text(getLocale("stop_rec", "Stop recording"));
  1922. $("#cb-enh-video-controls-record").find("span").data("ce-loc", "stop_rec");
  1923. $("#cb-enh-video-controls-record").addClass("cb-enh-active");
  1924. let date = new Date();
  1925. startRecordingStream(stream, lengthInMS, mimeType).then((recordedChunks) => {
  1926. let recordedBlob = new Blob(recordedChunks, {
  1927. type: mimeType
  1928. });
  1929. let objectURL = URL.createObjectURL(recordedBlob);
  1930. let link = document.createElement('a');
  1931. link.download = getFileName(username, '.' + fileExt, date);
  1932. link.href = objectURL;
  1933. link.click();
  1934. gRecordingStream = null;
  1935. gRecording = false;
  1936. setTimeout(function() {
  1937. if(objectURL) {
  1938. URL.revokeObjectURL(objectURL);
  1939. }
  1940. }, 10000);
  1941. if(!gRecordingCanceledByUser) {
  1942. startRecording();
  1943. return;
  1944. }
  1945. gRecordingCanceledByUser = false;
  1946. $("#cb-enh-video-controls-record").find("span").text(getLocale("start_rec", "Start recording"));
  1947. $("#cb-enh-video-controls-record").find("span").data("ce-loc", "start_rec");
  1948. $("#cb-enh-video-controls-record").removeClass("cb-enh-active");
  1949. });
  1950. }
  1951. function errRecordingNotSupported() {
  1952. gRecording = false;
  1953. 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!'));
  1954. }
  1955. function wait(delayInMS) {
  1956. return new Promise((resolve) => setTimeout(resolve, delayInMS));
  1957. }
  1958. function startRecordingStream(stream, lengthInMS, mimeType) {
  1959. let options = {
  1960. mimeType: mimeType,
  1961. videoBitsPerSecond: 6000000
  1962. };
  1963. let recorder = new MediaRecorder(stream, options);
  1964. let data = [];
  1965. recorder.ondataavailable = (event) => { data.push(event.data); }
  1966. recorder.start(250);
  1967. gRecordingStream = stream;
  1968. let stopped = new Promise((resolve, reject) => {
  1969. recorder.onstop = (event) => { resolve(event.name); }
  1970. recorder.onerror = (event) => { reject(event.name); }
  1971. });
  1972. let recorded = wait(lengthInMS).then(() => {
  1973. if(recorder.state === "recording") {
  1974. recorder.stop();
  1975. }
  1976. });
  1977. return Promise.any([stopped, recorded]).then(() => data);
  1978. }
  1979. function stopRecordingStream(stream) {
  1980. stream.getTracks().forEach((track) => track.stop());
  1981. }
  1982. let acre4 = 'o8R1AMIgnTLIAWOVr3kX';
  1983. function stopRecording() {
  1984. if(!gRecording || !gRecordingStream) {
  1985. return;
  1986. }
  1987. gRecordingCanceledByUser = true;
  1988. stopRecordingStream(gRecordingStream);
  1989. }
  1990. function updateRoomListStatuses() {
  1991. $('.room_list_room').each(function() {
  1992. let room = $(this).find('a').eq(0).data('room');
  1993. let _this = this;
  1994. $.getJSON('https://chaturbate.com/api/biocontext/' + room + '/', function(data) {
  1995. if(data['room_status'] === 'public') {
  1996. let $label = $(_this).find('.thumbnail_label');
  1997. if($label.length === 0) {
  1998. $label = $(_this).find('.thumbnail_label_featured');
  1999. }
  2000. if(!$label.hasClass('thumbnail_label_c_new') && !$label.hasClass('thumbnail_label_c_gaming')) {
  2001. $label.remove();
  2002. }
  2003. return;
  2004. }
  2005. $(_this).find('.thumbnail_label').remove();
  2006. $(_this).find('.thumbnail_label_featured').remove();
  2007. let $label = $('<div class="thumbnail_label ce-loc"></div>');
  2008. $label.text( getLocale('status_' + data['room_status'], data['room_status']) );
  2009. if(data['room_status'] === 'private' || data['room_status'] === 'group' || data['room_status'] === 'hidden') {
  2010. $label.addClass('thumbnail_label_c_private_show');
  2011. }
  2012. else if(data['room_status'] === 'offline') {
  2013. $label.addClass('thumbnail_label_offline');
  2014. }
  2015. else {
  2016. $label.addClass('thumbnail_label_c');
  2017. }
  2018. $(_this).append($label);
  2019. });
  2020. });
  2021. }
  2022. function enhanceFollowedList() {
  2023. clearIntervalEx(intvUpdateFollowedList);
  2024. updateRoomListStatuses();
  2025. intvUpdateFollowedList = setInterval(function() {
  2026. updateRoomListStatuses();
  2027. }, 1000 * 30);
  2028. }
  2029. function setGridSize(width) {
  2030. let gridStyle = `#room_list, #roomlist_root .roomlist_container ul.list {grid-template-columns: repeat(auto-fill,minmax(` + width + `px,max-content)) !important;}`;
  2031. addStyle(gridStyle);
  2032. }
  2033. function setMoreRoomsGridSize(width) {
  2034. let gridStyle = `.MoreRooms .list .roomCard {width: ` + width + `px;}`;
  2035. addStyle(gridStyle);
  2036. }
  2037. 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>';
  2038. 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>';
  2039. 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>';
  2040. 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>';
  2041. function initGridSizeSelector() {
  2042. if(gIsInMultiView) {
  2043. return;
  2044. }
  2045. let $topSelection = $('.top-section');
  2046. if($topSelection.length === 0) {
  2047. }
  2048. let $svg1 = $(gGridIconSvgBig);
  2049. $svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
  2050. $topSelection.append($svg1);
  2051. $svg1.on('click', function() {
  2052. setGridSize(280);
  2053. setSetting('grid-size', 280);
  2054. });
  2055. let $svg2 = $(gGridIconSvgMedium);
  2056. $svg2.addClass('cb-enh-grid-size-selector-img');
  2057. $svg2.on('click', function() {
  2058. setGridSize(250);
  2059. setSetting('grid-size', 250);
  2060. });
  2061. $topSelection.append($svg2);
  2062. let $svg3 = $(gGridIconSvgSmall);
  2063. $svg3.addClass('cb-enh-grid-size-selector-img');
  2064. $topSelection.append($svg3);
  2065. $svg3.on('click', function() {
  2066. setGridSize(220);
  2067. setSetting('grid-size', 220);
  2068. });
  2069. let $svg4 = $(gGridIconSvgSmallX);
  2070. $svg4.addClass('cb-enh-grid-size-selector-img');
  2071. $topSelection.append($svg4);
  2072. $svg4.on('click', function() {
  2073. setGridSize(180);
  2074. setSetting('grid-size', 180);
  2075. });
  2076. let intvWaitMoreRooms = setInterval(function() {
  2077. let moreRoomsSel = '.MoreRooms';
  2078. let $moreRooms = $(moreRoomsSel);
  2079. if($moreRooms.length === 0) {
  2080. return;
  2081. }
  2082. clearIntervalEx(intvWaitMoreRooms);
  2083. let $blockOuter = $('<div id="cb-enh-more-rooms-size-selectors"></div>');
  2084. let $msvg4 = $(gGridIconSvgSmallX);
  2085. $msvg4.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
  2086. $blockOuter.prepend($msvg4);
  2087. $msvg4.on('click', function() {
  2088. setMoreRoomsGridSize(180);
  2089. setSetting('grid-more-size', 180);
  2090. });
  2091. let $msvg3 = $(gGridIconSvgSmall);
  2092. $msvg3.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
  2093. $blockOuter.prepend($msvg3);
  2094. $msvg3.on('click', function() {
  2095. setMoreRoomsGridSize(210);
  2096. setSetting('grid-more-size', 210);
  2097. });
  2098. let $msvg2 = $(gGridIconSvgMedium);
  2099. $msvg2.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img');
  2100. $blockOuter.prepend($msvg2);
  2101. $msvg2.on('click', function() {
  2102. setMoreRoomsGridSize(260);
  2103. setSetting('grid-more-size', 260);
  2104. });
  2105. let $msvg1 = $(gGridIconSvgBig);
  2106. $msvg1.addClass('cb-enh-grid-size-selector-img cb-enh-grid-size-selector-more-img cb-enh-first');
  2107. $blockOuter.prepend($msvg1);
  2108. $msvg1.on('click', function() {
  2109. setMoreRoomsGridSize(300);
  2110. setSetting('grid-more-size', 300);
  2111. });
  2112. $blockOuter.insertBefore(moreRoomsSel);
  2113. }, 50);
  2114. }
  2115. 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><li style="display: block; float: right;"><span href="#" target="_blank" rel="noopener" style="color: rgb(255, 255, 255); font: 400 13.999px ubuntumedium, Arial, Helvetica, sans-serif; cursor: pointer;" id="cb-enh-toggle-settings">CHATURBATE ENHANCER <span class="ce-loc" data-ce-loc="settings">Settings</span></span></li>`;
  2116. 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>';
  2117. function initMultiViewUIRoom() {
  2118. let $linkContainer = $('.BaseRoomContents div.defaultColor.styledDiv');
  2119. if($linkContainer.length === 0) {
  2120. return;
  2121. }
  2122. $linkContainer = $linkContainer.eq(0);
  2123. let $link = $('<span><span id="cb-enh-add-multi-link" class="link ce-loc" data-ce-loc="multi_add">Add to multi cam viewer<span></span>');
  2124. $linkContainer.append($link);
  2125. $linkContainer.css('display', 'unset');
  2126. $(document).on('click', '#cb-enh-add-multi-link', function() {
  2127. multiAddRoomToList(gCurrentBroadcaster);
  2128. });
  2129. }
  2130. function multiAddUIAddButtons() {
  2131. $('ul.list li.roomCard').each(function() {
  2132. if($(this).data('cb-enh-multi-icon-added')) {
  2133. return;
  2134. }
  2135. $(this).data('cb-enh-multi-icon-added', true);
  2136. let $span = $('<span class="cb-enh-room-more-icon"></span>');
  2137. $span.prop('title', getLocale('show_more_menu', 'Show more menu'));
  2138. let $addIcon = $(gMoreSVG);
  2139. $span.append($addIcon);
  2140. $(this).find('.sub-info .cams').append($span);
  2141. $span = $('<span class="cb-enh-add-cam-icon"></span>');
  2142. $span.prop('title', getLocale('multi_add', 'Add to multi cam viewer'));
  2143. $addIcon = $(multiAddSVG);
  2144. $span.append($addIcon);
  2145. $(this).find('.sub-info .cams').append($span);
  2146. });
  2147. }
  2148. let gRoomMoreMenuVisible = false;
  2149. let gMoreRoomMenuUsername = null;
  2150. function initMultiViewUINavigation() {
  2151. let $nav = $('#nav');
  2152. if($nav.length !== 0) {
  2153. let $multiLink = $(multiLinkHTML);
  2154. $nav.eq(0).append($multiLink);
  2155. }
  2156. $(document).on('click', '.cb-enh-add-cam-icon', function() {
  2157. let username = $(this).closest('.roomCard').find('a').eq(0).data('room');
  2158. if(username) {
  2159. multiAddRoomToList(username);
  2160. }
  2161. });
  2162. $(document).on('click', '.cb-enh-room-more-icon', function() {
  2163. let username = $(this).closest('.roomCard').find('a').eq(0).data('room');
  2164. let $menu = $('#cb-enh-room-more-menu');
  2165. if(gRoomMoreMenuVisible && gMoreRoomMenuUsername === username) {
  2166. setRoomMoreMenuVisible(false);
  2167. }
  2168. else {
  2169. gMoreRoomMenuUsername = username;
  2170. $menu.data('username', username);
  2171. let pos = $(this).offset();
  2172. let x = pos.left;
  2173. let y = pos.top + 22;
  2174. if(x > $(window).width() - 120 - 20) {
  2175. x = $(window).width() - 120 - 20;
  2176. }
  2177. setRoomMoreMenuVisible(true, x, y);
  2178. }
  2179. });
  2180. $(document).on('click', '.cb-enh-st-blocklist-username-remove-icon', function() {
  2181. let username = $(this).data('username');
  2182. blocklistRemove(username);
  2183. $(this).parent().remove();
  2184. });
  2185. $(document).on('click', '#cb-enh-toggle-settings', function() {
  2186. setSettingsOpen(true);
  2187. });
  2188. $(document).on('click', '#cb-enh-page-overlay', function() {
  2189. setSettingsOpen(false);
  2190. });
  2191. $(document).on('click', '#cb-enh-settings-window-close-icon', function() {
  2192. setSettingsOpen(false);
  2193. });
  2194. multiAddUIAddButtons();
  2195. setInterval(function() {
  2196. multiAddUIAddButtons();
  2197. }, 1000);
  2198. }
  2199. let multiViewStyle = `#cb-enh-multi-content {width: 100% !important;}#cb-enh-multi-content h1 {font-size: 26px;margin-left: 10px;float: left;}.darkmode #cb-enh-multi-content, #cb-enh-multi-content.cb-enh-multi-fullscreen-full {color: #f7f7f7;}.cb-enh-multi-cam {display: none;float: left;position: relative;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);}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-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: 28px;font-size: 18px;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-follow {background: url(https://static-assets.highwebmedia.com/images/following-off.png) no-repeat;position: absolute;top: 0;left: 2px;opacity: 0;transition: opacity 0.25s;cursor: pointer;width: 24px;height: 24px;background-size: 100% 100%;}.cb-enh-multi-follow-followed {background-image: url(https://static-assets.highwebmedia.com/images/following-hover.png);}.cb-enh-multi-follow:hover {background-image: url(https://static-assets.highwebmedia.com/images/following-off-hover.png);}.cb-enh-multi-follow-update {background-image: url(https://static-assets.highwebmedia.com/images/following-update.gif) !important;}.cb-enh-multi-subject {display: none;position: absolute;top: 4px;left: 26px;font-size: 12px;width: calc(100% - 28px - 24px);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-offline .cb-enh-multi-subject {top: 25px;width: 100%;width: calc(100% - 4px - 4px);left: 4px;}body.cb-enh-multi-show-subject .cb-enh-multi-subject {display: block;}.cb-enh-multi-close {position: absolute;top: 0;right: 7px;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-resize-handle {width: 8px; position: absolute; top: 0; right: 0; height: 100%;cursor: ew-resize;background: #e0e0e0 url(https://static-assets.highwebmedia.com/tsdefaultassets/resize_arrows.svg) no-repeat center/325%;opacity: 0;transition: opacity 0.25s;}.darkmode .cb-enh-multi-resize-handle {background: #17202A url(https://static-assets.highwebmedia.com/tsdefaultassets/resize_arrows_dm.svg) no-repeat center/325%;}.cb-enh-multi-cam-hover .cb-enh-multi-resize-handle {opacity: 0.8;}.cb-enh-multi-cam-hover .cb-enh-multi-username, .cb-enh-multi-cam-hover .cb-enh-multi-follow, .cb-enh-multi-cam-hover .cb-enh-multi-status, .cb-enh-multi-cam-hover .cb-enh-multi-subject, .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-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;}.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;}.advanced-search-button-container {display: none;}`;
  2200. 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"><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 cams</label><br><input type="checkbox" id="cb-enh-multi-btn-auto-hide-priv"><label for="cb-enh-multi-btn-auto-hide-priv" class="noselect ce-loc" data-ce-loc="multi_auto_hide_priv">Hide 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><input type="checkbox" id="cb-enh-multi-show-subject"><label for="cb-enh-multi-show-subject" class="noselect ce-loc" data-ce-loc="multi_show_subject">Show room subjects</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_resize">Use resize bar to resize cams.</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>`;
  2201. 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-follow noselect"></div><div class="cb-enh-multi-status noselect"></div><div class="cb-enh-multi-subject noselect"></div><div class="cb-enh-multi-refresh noselect">{{refreshSVG}}</div><div class="cb-enh-multi-resize-handle noselect"></div><div class="cb-enh-multi-close noselect">{{closeSVG}}</div></div></div></div>`;
  2202. 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>';
  2203. let gMultiRooms = [];
  2204. let gMultiRoomsData = [];
  2205. let gMultiInMiniFullscreen = false;
  2206. let gIntvMultiUpdateRooms;
  2207. let gMultiIsDragging = false;
  2208. let gMultiIsResizing = false;
  2209. let gMultiResizeCurrentCam = null;
  2210. let gMultiResizeStartPos = null;
  2211. let gMultiDropReplace = true;
  2212. let $gMultiHoverBlock = null;
  2213. let gMultiHoverTime = 2500;
  2214. function initMultiView() {
  2215. gIsInMultiView = true;
  2216. clearAllTimeouts();
  2217. clearAllIntervals();
  2218. let hash = window.location.hash;
  2219. let roomsParamStart = hash.indexOf('?rooms=');
  2220. if(roomsParamStart !== -1) {
  2221. let rooms = hash.substring(roomsParamStart + '?rooms='.length).split(',');
  2222. multiClearRoomList();
  2223. rooms.forEach(function(room) {
  2224. if(!room.match(/^[a-zA-Z0-9_]+$/)) {
  2225. return;
  2226. }
  2227. multiAddRoomToList(room);
  2228. });
  2229. }
  2230. addStyle(multiViewStyle);
  2231. $('#main').append(multiViewHTML);
  2232. $(document).on('click', '.cb-enh-multi-close', function() {
  2233. let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
  2234. multiRemoveRoom(username);
  2235. });
  2236. $(document).on('click', '.cb-enh-multi-follow', function() {
  2237. if($(this).hasClass('cb-enh-multi-follow-update')) {
  2238. return;
  2239. }
  2240. let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
  2241. let csrf = getCSRFToken();
  2242. $(this).addClass('cb-enh-multi-follow-update');
  2243. let isFollowing = $(this).hasClass('cb-enh-multi-follow-followed');
  2244. let _this = this;
  2245. function callbackOk(isFollowing) {
  2246. $(_this).removeClass('cb-enh-multi-follow-update');
  2247. $(_this).toggleClass('cb-enh-multi-follow-followed', isFollowing);
  2248. }
  2249. function callbackErr() {
  2250. $(_this).removeClass('cb-enh-multi-follow-update');
  2251. }
  2252. setFollow(username, !isFollowing, callbackOk, callbackErr);
  2253. });
  2254. function resizeCam(e) {
  2255. cursorX = e.pageX;
  2256. cursorY = e.pageY;
  2257. if(cursorX > document.body.clientWidth) {
  2258. return;
  2259. }
  2260. let width = cursorX - gMultiResizeStartPos;
  2261. if(width < 100) {
  2262. width = 100;
  2263. }
  2264. let columnEnd = Math.round( (width / document.body.clientWidth) * multiGridColumnCount );
  2265. gMultiResizeCurrentCam.css('grid-column', 'span ' + columnEnd.toString());
  2266. let widthReal = (columnEnd / multiGridColumnCount) * document.body.clientWidth;
  2267. let height = widthReal * 9/16
  2268. let rowCount = Math.round(height);
  2269. gMultiResizeCurrentCam.css('grid-row', 'span ' + rowCount.toString());
  2270. }
  2271. $(document).on('mousedown', '.cb-enh-multi-resize-handle', function() {
  2272. gMultiIsResizing = true;
  2273. gMultiResizeCurrentCam = $(this).closest('.cb-enh-multi-cam');
  2274. gMultiResizeStartPos = gMultiResizeCurrentCam.position().left;
  2275. document.body.style.cursor = 'ew-resize';
  2276. addEventListener('mousemove', resizeCam);
  2277. });
  2278. $(document).on('mouseup', function() {
  2279. if(!gMultiIsResizing) {
  2280. return;
  2281. }
  2282. gMultiIsResizing = false;
  2283. removeEventListener('mousemove', resizeCam);
  2284. document.body.style.cursor = 'unset';
  2285. });
  2286. $(document).on('focus', '#cb-enh-multi-share', function() {
  2287. this.setSelectionRange(0, this.value.length);
  2288. });
  2289. addEventListener('storage', function(e) {
  2290. if(e.key !== 'cb-enh-multicam') {
  2291. return;
  2292. }
  2293. multiUpdateRooms();
  2294. });
  2295. multiUpdateRooms();
  2296. let $selectorDiv = $('#cb-enh-multi-size-selector');
  2297. let $svg1 = $(gGridIconSvgBig);
  2298. $svg1.addClass('cb-enh-grid-size-selector-img cb-enh-first');
  2299. $selectorDiv.append($svg1);
  2300. $svg1.on('click', function() {
  2301. let width = 2;
  2302. multiSetGridSize(width);
  2303. setSetting('multi-grid-size', width);
  2304. });
  2305. let $svg2 = $(gGridIconSvgMedium);
  2306. $svg2.addClass('cb-enh-grid-size-selector-img');
  2307. $svg2.on('click', function() {
  2308. let columns = 3;
  2309. multiSetGridSize(columns);
  2310. setSetting('multi-grid-size', columns);
  2311. });
  2312. $selectorDiv.append($svg2);
  2313. let $svg3 = $(gGridIconSvgSmall);
  2314. $svg3.addClass('cb-enh-grid-size-selector-img');
  2315. $selectorDiv.append($svg3);
  2316. $svg3.on('click', function() {
  2317. let columns = 4;
  2318. multiSetGridSize(columns);
  2319. setSetting('multi-grid-size', columns);
  2320. });
  2321. let $svg4 = $(gGridIconSvgSmallX);
  2322. $svg4.addClass('cb-enh-grid-size-selector-img');
  2323. $selectorDiv.append($svg4);
  2324. $svg4.on('click', function() {
  2325. let columns = 5;
  2326. multiSetGridSize(columns);
  2327. setSetting('multi-grid-size', columns);
  2328. });
  2329. $(document).on('click', '#cb-enh-multi-btn-remove-offline', function() {
  2330. multiRemoveOfflineRooms();
  2331. });
  2332. $(document).on('click', '#cb-enh-multi-btn-remove-all', function() {
  2333. multiRemoveAllRooms();
  2334. });
  2335. $(document).on('click', '#cb-enh-multi-btn-mute-all', function() {
  2336. $('.cb-enh-multi-cam video').each(function() {
  2337. this.muted = true;
  2338. });
  2339. });
  2340. $(document).on('change', '#cb-enh-multi-btn-auto-remove', function() {
  2341. let enabled = $(this).is(":checked");
  2342. setSetting('multi-auto-remove', enabled);
  2343. if(enabled) {
  2344. multiRemoveOfflineRooms();
  2345. }
  2346. });
  2347. $(document).on('change', '#cb-enh-multi-btn-auto-hide', function() {
  2348. let enabled = $(this).is(":checked");
  2349. setSetting('multi-auto-hide', enabled);
  2350. multiHidePrivateRoomsIfNeeded();
  2351. });
  2352. $(document).on('change', '#cb-enh-multi-btn-auto-hide-priv', function() {
  2353. let enabled = $(this).is(":checked");
  2354. setSetting('multi-auto-hide-priv', enabled);
  2355. multiHidePrivateRoomsIfNeeded();
  2356. });
  2357. $(document).on('change', '#cb-enh-multi-show-subject', function() {
  2358. let enabled = $(this).is(":checked");
  2359. setSetting('multi-show-subject', enabled);
  2360. $(document.body).toggleClass('cb-enh-multi-show-subject', enabled);
  2361. });
  2362. $(document).on('change', '#cb-enh-multi-max-quality', function() {
  2363. let maxHeight = parseInt($(this).val());
  2364. setSetting('multi-max-quality', maxHeight);
  2365. $('.cb-enh-multi-cam video').each(function() {
  2366. let username = $(this).closest('.cb-enh-multi-cam').data('cb-enh-username');
  2367. let hls = gMultiRoomsData[username]['hls'];
  2368. if(hls) {
  2369. multiUpdateVideoQuality(hls, maxHeight);
  2370. }
  2371. });
  2372. });
  2373. let autoRemove = getSetting('multi-auto-remove');
  2374. $('#cb-enh-multi-btn-auto-remove').prop('checked', autoRemove);
  2375. let autoHideOffline = getSetting('multi-auto-hide');
  2376. $('#cb-enh-multi-btn-auto-hide').prop('checked', autoHideOffline);
  2377. let autoHidePriv = getSetting('multi-auto-hide-priv');
  2378. $('#cb-enh-multi-btn-auto-hide-priv').prop('checked', autoHidePriv);
  2379. let showSubject = getSetting('multi-show-subject');
  2380. if(showSubject === null) {
  2381. showSubject = true;
  2382. }
  2383. $('#cb-enh-multi-show-subject').prop('checked', showSubject);
  2384. $(document.body).toggleClass('cb-enh-multi-show-subject', showSubject);
  2385. $('#cb-enh-multi-max-quality').val(multiGetMaxHeight());
  2386. $(document).on('click', '#cb-enh-multi-btn-enter-fullscreen', function() {
  2387. if(!document.fullscreenElement) {
  2388. multiEnterFullscreen();
  2389. }
  2390. else {
  2391. document.exitFullscreen();
  2392. }
  2393. });
  2394. $(document).on('click', '#cb-enh-multi-btn-enter-fullscreen-mini', function() {
  2395. multiToggleFullscreenMini();
  2396. });
  2397. document.addEventListener('fullscreenchange', multiOnFullscreenChange, false);
  2398. document.addEventListener('mozfullscreenchange', multiOnFullscreenChange, false);
  2399. document.addEventListener('MSFullscreenChange', multiOnFullscreenChange, false);
  2400. document.addEventListener('webkitfullscreenchange', multiOnFullscreenChange, false);
  2401. window.addEventListener('keydown', function(e) {
  2402. let keyCode = e.keyCode || e.which;
  2403. if(keyCode === 122) {
  2404. multiEnterFullscreen();
  2405. e.preventDefault();
  2406. e.stopImmediatePropagation();
  2407. return;
  2408. }
  2409. if(keyCode === 121 && !e.repeat) {
  2410. multiToggleFullscreenMini();
  2411. e.preventDefault();
  2412. e.stopImmediatePropagation();
  2413. return;
  2414. }
  2415. if(keyCode === 27 && !e.repeat) {
  2416. if(gMultiInMiniFullscreen) {
  2417. multiToggleFullscreenMini();
  2418. e.preventDefault();
  2419. e.stopImmediatePropagation();
  2420. return;
  2421. }
  2422. if(gMultiIsDragging) {
  2423. multiOnDragEnd(true)
  2424. e.preventDefault();
  2425. e.stopImmediatePropagation();
  2426. return;
  2427. }
  2428. }
  2429. });
  2430. multiUpdateShareURL();
  2431. clearIntervalEx(gIntvMultiUpdateRooms);
  2432. gIntvMultiUpdateRooms = setInterval(multiUpdateRoomStatuses, 1000 * 30);
  2433. $('#cb-enh-multi-tip-add-icon').append(multiAddSVG);
  2434. let intvClearHover = null;
  2435. function clearHover() {
  2436. $gMultiHoverBlock.removeClass('cb-enh-multi-cam-hover');
  2437. }
  2438. let cursorX = 0;
  2439. let cursorY = 0;
  2440. let $dragBlock = null;
  2441. let $dragHoverBlock = null;
  2442. function updateDragImagePosition() {
  2443. let $dragImg = $('#cb-enh-multi-drag-img');
  2444. let width = $dragImg.outerWidth();
  2445. let height = $dragImg.outerHeight();
  2446. $dragImg.css('left', cursorX - width / 2);
  2447. $dragImg.css('top', cursorY - height / 2 - window.scrollY);
  2448. }
  2449. addEventListener('mousemove', function(e) {
  2450. cursorX = e.pageX;
  2451. cursorY = e.pageY;
  2452. if(gMultiIsDragging) {
  2453. updateDragImagePosition();
  2454. }
  2455. if($gMultiHoverBlock && !$gMultiHoverBlock.hasClass('cb-enh-multi-cam-hover')) {
  2456. $gMultiHoverBlock.addClass('cb-enh-multi-cam-hover');
  2457. intvClearHover = setTimeout(clearHover, gMultiHoverTime);
  2458. }
  2459. });
  2460. function multiDrop() {
  2461. let oldScrollY = window.scrollY;
  2462. let index = $dragHoverBlock.index();
  2463. let oldIndex = $dragBlock.index();
  2464. let indexRaw = index;
  2465. let oldIndexRaw = oldIndex;
  2466. if(!gMultiDropReplace) {
  2467. let arrMin1 = false;
  2468. if(index === 0) {
  2469. $dragBlock.prependTo($('#cb-enh-multi-cams'));
  2470. }
  2471. else {
  2472. if(index > oldIndex) {
  2473. index += 1;
  2474. arrMin1 = true;
  2475. }
  2476. $dragBlock.insertAfter($('.cb-enh-multi-cam').eq(index - 1));
  2477. }
  2478. if(arrMin1) {
  2479. index -= 1;
  2480. }
  2481. }
  2482. else {
  2483. let arrMin1 = false;
  2484. if(index === 0) {
  2485. $dragBlock.prependTo($('#cb-enh-multi-cams'));
  2486. }
  2487. else {
  2488. if(index > oldIndex) {
  2489. index += 1;
  2490. arrMin1 = true;
  2491. }
  2492. $dragBlock.insertAfter($('.cb-enh-multi-cam').eq(index - 1));
  2493. }
  2494. if(arrMin1) {
  2495. index -= 1;
  2496. oldIndex -= 1;
  2497. }
  2498. if(oldIndex < 0) {
  2499. $dragHoverBlock.prependTo($('#cb-enh-multi-cams'));
  2500. }
  2501. else {
  2502. $dragHoverBlock.insertAfter($('.cb-enh-multi-cam').eq(oldIndex));
  2503. }
  2504. let dragBlockColumn = $dragBlock.css('grid-column');
  2505. let dragBlockRow = $dragBlock.css('grid-row');
  2506. $dragBlock.css( 'grid-column', $dragHoverBlock.css('grid-column') );
  2507. $dragBlock.css( 'grid-row', $dragHoverBlock.css('grid-row') );
  2508. $dragHoverBlock.css( 'grid-column', dragBlockColumn );
  2509. $dragHoverBlock.css( 'grid-row', dragBlockRow );
  2510. }
  2511. let temp = gMultiRooms[indexRaw];
  2512. gMultiRooms[indexRaw] = gMultiRooms[oldIndexRaw];
  2513. gMultiRooms[oldIndexRaw] = temp;
  2514. window.scrollTo(0, oldScrollY);
  2515. let data = localStorage.getItem('cb-enh-multicam');
  2516. try {
  2517. data = JSON.parse(data);
  2518. }
  2519. catch(SyntaxError) {
  2520. data = null;
  2521. }
  2522. if(!data) {
  2523. data = {
  2524. 'rooms': []
  2525. };
  2526. }
  2527. data['rooms'] = gMultiRooms;
  2528. localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
  2529. multiUpdateShareURL();
  2530. }
  2531. function multiOnDragEnd(cancelled) {
  2532. gMultiIsDragging = false;
  2533. let $dragImg = $('#cb-enh-multi-drag-img');
  2534. $dragImg.hide();
  2535. $dragImg.html('');
  2536. $dragBlock.removeClass('cb-enh-multi-cam-dragging');
  2537. $('.cb-enh-multi-cam').removeClass('cb-enh-multi-cam-ondragover');
  2538. document.body.style.cursor = '';
  2539. if(!cancelled && $dragHoverBlock && $dragHoverBlock.length !== 0 && !$dragBlock.is($dragHoverBlock)) {
  2540. multiDrop();
  2541. }
  2542. $dragBlock.removeClass('cb-enh-multi-cam-dragging');
  2543. $dragBlock = null;
  2544. $dragHoverBlock = null;
  2545. }
  2546. addEventListener('pointerup', function(e) {
  2547. if(!gMultiIsDragging) {
  2548. return;
  2549. }
  2550. multiOnDragEnd(false);
  2551. });
  2552. $(document).on('dragstart', '.cb-enh-multi-cam', function(e) {
  2553. e = e.originalEvent;
  2554. e.preventDefault();
  2555. e.stopImmediatePropagation();
  2556. if(gMultiIsResizing) {
  2557. return;
  2558. }
  2559. $dragBlock = $(e.target).closest('.cb-enh-multi-cam');
  2560. $dragBlock.addClass('cb-enh-multi-cam-dragging');
  2561. let $dragImg = $('#cb-enh-multi-drag-img');
  2562. let video = $dragBlock.find('video')[0];
  2563. if(!video) {
  2564. let $img = $dragBlock.find('img.cb-enh-multi-cam-snapshot');
  2565. if($img.length > 0) {
  2566. $img = $img.clone();
  2567. if($dragBlock.hasClass('cb-enh-multi-offline')) {
  2568. $img.addClass('cb-enh-multi-offline');
  2569. }
  2570. $dragImg.html($img);
  2571. }
  2572. }
  2573. else {
  2574. let canvas = captureVideoFrame(video);
  2575. $dragImg.html(canvas);
  2576. }
  2577. gMultiIsDragging = true;
  2578. updateDragImagePosition();
  2579. $dragImg.show();
  2580. document.body.style.cursor = 'move';
  2581. });
  2582. $(document).on('mouseenter', '.cb-enh-multi-cam', function() {
  2583. $(this).addClass('cb-enh-multi-cam-hover');
  2584. $gMultiHoverBlock = $(this);
  2585. intvClearHover = setTimeout(clearHover, gMultiHoverTime);
  2586. if(!gMultiIsDragging) {
  2587. return;
  2588. }
  2589. let $parent = $(this).closest('.cb-enh-multi-cam');
  2590. if(!$dragBlock.is($parent)) {
  2591. $($parent).addClass('cb-enh-multi-cam-ondragover');
  2592. $dragHoverBlock = $parent;
  2593. }
  2594. else {
  2595. $dragHoverBlock = null;
  2596. }
  2597. });
  2598. $(document).on('mouseleave', '.cb-enh-multi-cam', function() {
  2599. if($gMultiHoverBlock) {
  2600. $(this).removeClass('cb-enh-multi-cam-hover');
  2601. clearIntervalEx(intvClearHover);
  2602. $gMultiHoverBlock = null;
  2603. }
  2604. if(!gMultiIsDragging) {
  2605. return;
  2606. }
  2607. let $parent = $(this).closest('.cb-enh-multi-cam');
  2608. $($parent).removeClass('cb-enh-multi-cam-ondragover');
  2609. if(!$dragBlock.is($parent)) {
  2610. $dragHoverBlock = null;
  2611. }
  2612. });
  2613. multiInitSupportInfo();
  2614. localizeStringsNextTick();
  2615. document.addEventListener('click', function(e) {
  2616. if(e.target.nodeName === 'A') {
  2617. if(!isFollowedCamsLink(e.target)) {
  2618. e.stopImmediatePropagation();
  2619. }
  2620. return;
  2621. }
  2622. if($(e.target).parents('a').length !== 0) {
  2623. if(!isFollowedCamsLink($(e.target).parents('a')[0])) {
  2624. e.stopImmediatePropagation();
  2625. }
  2626. return;
  2627. }
  2628. }, true);
  2629. window.addEventListener('focus', function() {
  2630. $('video').each(function() {
  2631. if(this.duration - this.currentTime > 40 && this.duration >= 2) {
  2632. this.currentTime = this.duration - 2;
  2633. }
  2634. });
  2635. });
  2636. window.addEventListener('resize', function() {
  2637. $('.cb-enh-multi-cam').each(function() {
  2638. // let width = Math.floor(document.body.clientWidth / gridSize);
  2639. let width = $(this).innerWidth();
  2640. let columnEnd = Math.round( (width / document.body.clientWidth) * multiGridColumnCount );
  2641. $(this).css('grid-column', 'span ' + columnEnd.toString());
  2642. let widthReal = (columnEnd / multiGridColumnCount) * document.body.clientWidth;
  2643. let height = widthReal * 9/16
  2644. let rowCount = Math.round(height);
  2645. $(this).css('grid-row', 'span ' + rowCount.toString());
  2646. });
  2647. });
  2648. document.title = 'Multi Cam Viewer - Chaturbate Enhancer';
  2649. setInterval(function() {
  2650. document.title = 'Multi Cam Viewer - Chaturbate Enhancer';
  2651. }, 200);
  2652. }
  2653. function multiInitSupportInfo() {
  2654. let userType = getUserType();
  2655. let msg = getSupportMessage(userType);
  2656. if(msg === null) {
  2657. $('#cb-enh-multi-support').hide();
  2658. return;
  2659. }
  2660. $(document).on('click', '#cb-enh-multi-support', function() {
  2661. loutrreg(gCurrentBroadcaster);
  2662. });
  2663. $('#cb-enh-multi-support').append(msg);
  2664. adra($('#cb-enh-multi-support'));
  2665. }
  2666. function isFollowedCamsLink(el) {
  2667. return el.getAttribute('href').startsWith('/followed-cams');
  2668. }
  2669. let acre5 = 'ugVacTnBjqAPShC2UAF1';
  2670. function multiUpdateRooms() {
  2671. let data = localStorage.getItem('cb-enh-multicam');
  2672. try {
  2673. data = JSON.parse(data);
  2674. }
  2675. catch(SyntaxError) {
  2676. return;
  2677. }
  2678. if(!data || !('rooms' in data)) {
  2679. return;
  2680. }
  2681. data['rooms'].forEach(function(room) {
  2682. if(gMultiRooms.indexOf(room) !== -1) {
  2683. return;
  2684. }
  2685. multiAddRoom(room);
  2686. });
  2687. }
  2688. function multiUpdateRoomStatuses() {
  2689. gMultiRooms.forEach(function(username) {
  2690. if(gMultiRoomsData[username]['is_loading']) {
  2691. return;
  2692. }
  2693. xmlhttpRequest({
  2694. method: 'GET',
  2695. url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
  2696. headers: {
  2697. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  2698. 'Referer': 'https://chaturbate.com/' + username + '/',
  2699. },
  2700. timeout: 60*1*1000,
  2701. onload: function(resp) {
  2702. let data;
  2703. try {
  2704. data = JSON.parse(resp.responseText);
  2705. }
  2706. catch(SyntaxError) {
  2707. return;
  2708. }
  2709. if(!(username in gMultiRoomsData)) {
  2710. return;
  2711. }
  2712. let $block = $('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]');
  2713. if($block.length === 0) {
  2714. return;
  2715. }
  2716. if(!('room_status' in data)) {
  2717. if('code' in data && data['code'] === 'password-required') {
  2718. multiSetErrored($block, getLocale('passworded', 'passworded'));
  2719. return;
  2720. }
  2721. return;
  2722. }
  2723. let status = data['room_status'];
  2724. gMultiRoomsData[username]['status'] = status;
  2725. $block.find('.cb-enh-multi-follow:not(.cb-enh-multi-follow-update)').toggleClass('cb-enh-multi-follow-followed', data['following']);
  2726. $block.find('.cb-enh-multi-subject').text(data['room_title']);
  2727. if(status !== 'public') {
  2728. let autoHideOffline = getSetting('multi-auto-hide');
  2729. let autoHidePriv = getSetting('multi-auto-hide-priv');
  2730. let statusText = getLocale('status_' + status, status);
  2731. multiSetErrored($block, statusText);
  2732. if(status === 'offline' && getSetting('multi-auto-remove')) {
  2733. multiRemoveRoom(username);
  2734. }
  2735. else {
  2736. let hide = (autoHideOffline && status === 'offline') || (autoHidePriv && status !== 'public' && status !== 'offline');
  2737. if(hide) {
  2738. $block.hide();
  2739. }
  2740. else {
  2741. $block.show();
  2742. }
  2743. }
  2744. return;
  2745. }
  2746. if(!('hls_source' in data) || data['hls_source'] === '') {
  2747. return;
  2748. }
  2749. let hls_source = fixCBHLSURL(data['hls_source']);
  2750. multiSetOk($block);
  2751. let $video = $block.find('video');
  2752. if($video.length === 0) {
  2753. multiInitVideo($block, username, hls_source);
  2754. }
  2755. else {
  2756. let trans = getCBTrans(hls_source);
  2757. if(trans && $video.data('trans') !== trans) {
  2758. gMultiRoomsData[username]['hls'].loadSource(hls_source);
  2759. }
  2760. }
  2761. }
  2762. });
  2763. });
  2764. }
  2765. function multiSetErrored($block, status) {
  2766. $block.find('.cb-enh-multi-status').text(status);
  2767. $block.addClass('cb-enh-multi-offline');
  2768. $block.find('video').remove();
  2769. if(getSetting('multi-auto-hide')) {
  2770. $block.hide();
  2771. }
  2772. else {
  2773. $block.show();
  2774. }
  2775. }
  2776. function multiSetOk($block) {
  2777. $block.find('.cb-enh-multi-status').text('');
  2778. $block.removeClass('cb-enh-multi-offline');
  2779. $block.show();
  2780. }
  2781. function multiInitVideo($block, username, hls_source) {
  2782. let videoHTML = '<video ';
  2783. if(!isFirefox()) {
  2784. videoHTML += 'controls ';
  2785. }
  2786. 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://jpeg.live.mmcdn.com/stream?room={{username}}&f={{random}}"></video>';
  2787. videoHTML = videoHTML.replaceAll('{{username}}', username);
  2788. videoHTML = videoHTML.replaceAll('{{random}}', Math.random());
  2789. let $video = $(videoHTML);
  2790. $video.data('trans', getCBTrans(hls_source));
  2791. let hls = new Hls();
  2792. gMultiRoomsData[username]['hls'] = hls;
  2793. hls.on(Hls.Events.LEVEL_LOADED, function() {
  2794. multiUpdateVideoQuality(hls);
  2795. });
  2796. hls.loadSource(hls_source);
  2797. hls.attachMedia($video[0]);
  2798. $block.find('.cb-enh-multi-cam-video-wrap').append($video);
  2799. }
  2800. function multiGetMaxHeight() {
  2801. let maxHeight = getSetting('multi-max-quality');
  2802. if(maxHeight === null) {
  2803. maxHeight = 1080;
  2804. }
  2805. return maxHeight;
  2806. }
  2807. function multiUpdateVideoQuality(hls) {
  2808. let maxHeight = multiGetMaxHeight();
  2809. let capped = false;
  2810. hls.levels.every(function(level, i) {
  2811. if(level.height > maxHeight) {
  2812. let levelIndex = i - 1;
  2813. if(levelIndex < 0) {
  2814. levelIndex = 0;
  2815. }
  2816. hls.autoLevelCapping = levelIndex;
  2817. capped = true;
  2818. return false;
  2819. }
  2820. return true;
  2821. });
  2822. if(!capped) {
  2823. hls.autoLevelCapping = -1;
  2824. }
  2825. }
  2826. function multiAddRoom(username) {
  2827. if(!username) {
  2828. return;
  2829. }
  2830. gMultiRoomsData[username] = {
  2831. 'is_loading': true,
  2832. 'status': null
  2833. };
  2834. gMultiRooms.push(username);
  2835. let blockHTML = multiViewCamHTML;
  2836. blockHTML = blockHTML.replaceAll('{{username}}', username);
  2837. blockHTML = blockHTML.replaceAll('{{random}}', Math.random());
  2838. blockHTML = blockHTML.replaceAll('{{closeSVG}}', closeSVG);
  2839. let $block = $(blockHTML);
  2840. $('#cb-enh-multi-cams').append($block);
  2841. let minGridSize = 2;
  2842. let maxGridSize = 10;
  2843. let gridSize = getSetting('multi-grid-size');
  2844. gridSize = parseInt(gridSize);
  2845. if(Number.isInteger(gridSize)) {
  2846. if(gridSize < minGridSize) {
  2847. gridSize = minGridSize;
  2848. }
  2849. else if(gridSize > maxGridSize) {
  2850. gridSize = maxGridSize
  2851. }
  2852. }
  2853. else {
  2854. gridSize = 3;
  2855. }
  2856. let width = Math.floor(document.body.clientWidth / gridSize);
  2857. let columnEnd = Math.round( (width / document.body.clientWidth) * multiGridColumnCount );
  2858. $block.css('grid-column', 'span ' + columnEnd.toString());
  2859. let widthReal = (columnEnd / multiGridColumnCount) * document.body.clientWidth;
  2860. let height = widthReal * 9/16
  2861. let rowCount = Math.round(height);
  2862. $block.css('grid-row', 'span ' + rowCount.toString());
  2863. xmlhttpRequest({
  2864. method: 'GET',
  2865. url: 'https://chaturbate.com/api/chatvideocontext/' + username + '/',
  2866. headers: {
  2867. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  2868. 'Referer': 'https://chaturbate.com/' + username + '/',
  2869. },
  2870. timeout: 60*1*1000,
  2871. onload: function(resp) {
  2872. let data;
  2873. try {
  2874. data = JSON.parse(resp.responseText);
  2875. }
  2876. catch(SyntaxError) {
  2877. multiSetErrored($block, getLocale('error', 'error'));
  2878. gMultiRoomsData[username]['is_loading'] = false;
  2879. return;
  2880. }
  2881. if(!('room_status' in data)) {
  2882. if('code' in data && data['code'] === 'password-required') {
  2883. multiSetErrored($block, getLocale('passworded', 'passworded'));
  2884. return;
  2885. }
  2886. multiSetErrored($block, getLocale('error', 'error'));
  2887. gMultiRoomsData[username]['is_loading'] = false;
  2888. return;
  2889. }
  2890. if($block.length === 0) {
  2891. gMultiRoomsData[username]['is_loading'] = false;
  2892. return;
  2893. }
  2894. let status = data['room_status']
  2895. gMultiRoomsData[username]['status'] = status;
  2896. let autoHideOffline = getSetting('multi-auto-hide');
  2897. let autoHidePriv = getSetting('multi-auto-hide-priv');
  2898. if(status !== 'public') {
  2899. let statusText = getLocale('status_' + status, status);
  2900. multiSetErrored($block, statusText);
  2901. if(status === 'offline' && getSetting('multi-auto-remove')) {
  2902. multiRemoveRoom(username);
  2903. }
  2904. }
  2905. else if(!('hls_source' in data) || data['hls_source'] === '') {
  2906. multiSetErrored($block, getLocale('multi_no_video_hls', 'error: no video available') );
  2907. }
  2908. else {
  2909. multiInitVideo($block, username, fixCBHLSURL(data['hls_source']));
  2910. }
  2911. let hide = (autoHideOffline && status === 'offline') || (autoHidePriv && status !== 'public' && status !== 'offline');
  2912. if(hide) {
  2913. $block.hide();
  2914. }
  2915. else {
  2916. $block.show();
  2917. }
  2918. $block.find('.cb-enh-multi-follow:not(.cb-enh-multi-follow-update)').toggleClass('cb-enh-multi-follow-followed', data['following']);
  2919. $block.find('.cb-enh-multi-subject').text(data['room_title']);
  2920. gMultiRoomsData[username]['is_loading'] = false;
  2921. },
  2922. onerror: function() {
  2923. if($block.length === 0) {
  2924. gMultiRoomsData[username]['is_loading'] = false;
  2925. return;
  2926. }
  2927. multiSetErrored($block, getLocale('error', 'error'));
  2928. if(getSetting('multi-auto-hide')) {
  2929. $block.hide();
  2930. }
  2931. else {
  2932. $block.show();
  2933. }
  2934. gMultiRoomsData[username]['is_loading'] = false;
  2935. }
  2936. });
  2937. fetchRoomSnapshot(username, function(resp) {
  2938. if($block.length === 0) {
  2939. return;
  2940. }
  2941. let $img = $('<img>');
  2942. $img.addClass('cb-enh-multi-cam-snapshot');
  2943. $img.attr('src', 'data:image/jpg;base64,' + btoa(String.fromCharCode.apply(null, new Uint8Array(resp))));
  2944. $block.find('.cb-enh-multi-cam-img-wrap').append($img);
  2945. });
  2946. insertRoomAv($block.find('.cb-enh-multi-cam-img-wrap'), username);
  2947. multiUpdateShareURL();
  2948. }
  2949. function multiRemoveRoom(username) {
  2950. $('.cb-enh-multi-cam[data-cb-enh-username="' + username + '"]').remove();
  2951. let pos = gMultiRooms.indexOf(username);
  2952. if(pos !== -1) {
  2953. gMultiRooms.splice(pos, 1);
  2954. delete gMultiRoomsData[username];
  2955. }
  2956. let data = {
  2957. 'rooms': gMultiRooms
  2958. };
  2959. localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
  2960. multiUpdateShareURL();
  2961. }
  2962. function multiAddRoomToList(username) {
  2963. if(!username) {
  2964. return;
  2965. }
  2966. let data = localStorage.getItem('cb-enh-multicam');
  2967. try {
  2968. data = JSON.parse(data);
  2969. }
  2970. catch(SyntaxError) {
  2971. data = null;
  2972. }
  2973. if(!data) {
  2974. data = {
  2975. 'rooms': []
  2976. };
  2977. }
  2978. if(data['rooms'].indexOf(username) !== -1) {
  2979. return;
  2980. }
  2981. data['rooms'].push(username);
  2982. localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
  2983. multiUpdateShareURL();
  2984. }
  2985. function multiClearRoomList() {
  2986. let data = localStorage.getItem('cb-enh-multicam');
  2987. try {
  2988. data = JSON.parse(data);
  2989. }
  2990. catch(SyntaxError) {
  2991. data = null;
  2992. }
  2993. if(!data) {
  2994. data = {
  2995. 'rooms': []
  2996. };
  2997. }
  2998. data['rooms'] = [];
  2999. localStorage.setItem('cb-enh-multicam', JSON.stringify(data));
  3000. }
  3001. function multiSetGridSize(columns) {
  3002. let width = Math.floor(document.body.clientWidth / columns);
  3003. let columnEnd = Math.round( (width / document.body.clientWidth) * multiGridColumnCount );
  3004. $('.cb-enh-multi-cam').css('grid-column', 'span ' + columnEnd.toString());
  3005. let widthReal = (columnEnd / multiGridColumnCount) * document.body.clientWidth;
  3006. let height = widthReal * 9/16
  3007. let rowCount = Math.round(height);
  3008. $('.cb-enh-multi-cam').css('grid-row', 'span ' + rowCount.toString());
  3009. }
  3010. function multiRemoveOfflineRooms() {
  3011. $('.cb-enh-multi-offline').each(function() {
  3012. let username = $(this).data('cb-enh-username');
  3013. if(gMultiRoomsData[username]['status'] !== 'offline') {
  3014. return;
  3015. }
  3016. multiRemoveRoom(username);
  3017. });
  3018. }
  3019. function multiHidePrivateRoomsIfNeeded() {
  3020. let autoHideOffline = getSetting('multi-auto-hide');
  3021. let autoHidePriv = getSetting('multi-auto-hide-priv');
  3022. $('.cb-enh-multi-cam').each(function() {
  3023. let username = $(this).data('cb-enh-username');
  3024. let status = gMultiRoomsData[username]['status'];
  3025. let hide = (autoHideOffline && status === 'offline') || (autoHidePriv && status !== 'public' && status !== 'offline');
  3026. if(hide) {
  3027. $(this).hide();
  3028. }
  3029. else {
  3030. $(this).show();
  3031. }
  3032. });
  3033. }
  3034. function multiRemoveAllRooms() {
  3035. $('.cb-enh-multi-cam').each(function() {
  3036. let username = $(this).data('cb-enh-username');
  3037. multiRemoveRoom(username);
  3038. });
  3039. }
  3040. function multiUpdateShareURL() {
  3041. let url = 'https://chaturbate.com/multicam/';
  3042. if(gMultiRooms.length !== 0) {
  3043. url += '?rooms=' + gMultiRooms.join(',');
  3044. }
  3045. $('#cb-enh-multi-share').val(url);
  3046. }
  3047. function multiEnterFullscreen() {
  3048. if(document.fullscreenElement) {
  3049. return;
  3050. }
  3051. if(gMultiInMiniFullscreen) {
  3052. multiToggleFullscreenMini();
  3053. }
  3054. $('#cb-enh-multi-content')[0].requestFullscreen();
  3055. $('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');
  3056. $('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen-full');
  3057. $('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
  3058. }
  3059. function multiToggleFullscreenMini() {
  3060. if(document.fullscreenElement) {
  3061. document.exitFullscreen();
  3062. }
  3063. let $btn = $('#cb-enh-multi-btn-enter-fullscreen-mini');
  3064. if(!gMultiInMiniFullscreen) {
  3065. $('#cb-enh-multi-content').addClass('cb-enh-multi-fullscreen');
  3066. $('#header').hide();
  3067. $('.top-section').hide();
  3068. $('#footer-holder').hide();
  3069. $btn.val( getLocale('multi_btn_exit_fscreen', 'EXIT FULLSCREEN') );
  3070. }
  3071. else {
  3072. $('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');
  3073. $('#header').show();
  3074. $('.top-section').show();
  3075. $('#footer-holder').show();
  3076. $btn.val( getLocale('multi_btn_enter_fscreen_mini', 'ENTER SEMI FULLSCREEN (WITH ADDRESS BAR)') );
  3077. }
  3078. gMultiInMiniFullscreen = !gMultiInMiniFullscreen;
  3079. window.scrollTo(0, 0);
  3080. }
  3081. function multiOnFullscreenChange() {
  3082. if(!document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
  3083. if(!gMultiInMiniFullscreen) {
  3084. $('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen');
  3085. }
  3086. $('#cb-enh-multi-content').removeClass('cb-enh-multi-fullscreen-full');
  3087. $('#cb-enh-multi-btn-enter-fullscreen').val( getLocale('multi_btn_enter_fscreen', 'ENTER FULLSCREEN') );
  3088. }
  3089. }
  3090. let notificationWindowHTML = `<div id="cb-enh-notification"><div id="cb-enh-notification-inner"><div id="cb-enh-notification-msg"></div></div></div>`;
  3091. let gIntvNotificationHide = null;
  3092. function showNotification(msg) {
  3093. let timeout = 5000;
  3094. $('#cb-enh-notification-msg').text(msg);
  3095. $('#cb-enh-notification').css('display', 'flex');
  3096. clearTimeoutEx(gIntvNotificationHide);
  3097. gIntvNotificationHide = setTimeout(function() {
  3098. $('#cb-enh-notification').css('display', 'none');
  3099. }, timeout);
  3100. }
  3101. function hideNotification() {
  3102. clearTimeoutEx(gIntvNotificationHide);
  3103. $('#cb-enh-notification').css('display', 'none');
  3104. }
  3105. function isInteractiveFullScreenEnabled() {
  3106. if(!$('#vjs_video_3').hasClass('vjs-controls-disabled')) {
  3107. return false;
  3108. }
  3109. let $player = $('.videoPlayerDiv');
  3110. return $player.innerWidth() === window.innerWidth && $player.innerHeight() === window.innerHeight;
  3111. }
  3112. function enterInteractiveFullScreen() {
  3113. $('#fvm-link').click();
  3114. }
  3115. function isTheaterModeEnabled() {
  3116. return $('#vjs_video_3').hasClass('vjs-controls-disabled');
  3117. }
  3118. function getFileName(fname, ext, date) {
  3119. let d = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
  3120. let t = ('0' + date.getHours()).slice(-2) + '-' + ('0' + date.getMinutes()).slice(-2) + '-' + ('0' + date.getSeconds()).slice(-2);
  3121. return fname + '_' + d + '_' + t + ext;
  3122. }
  3123. function getCBTrans(url) {
  3124. let spos1 = url.search('_trns_');
  3125. if(spos1 === -1) {
  3126. return;
  3127. }
  3128. let spos2 = url.substring(0, spos1).lastIndexOf('-');
  3129. if(spos2 === -1) {
  3130. return;
  3131. }
  3132. return url.substring(spos2 + 1, spos1);
  3133. }
  3134. function fixCBHLSURL(url) {
  3135. url = url.replace('/live-edge/', '/live-fhls/');
  3136. url = url.replace('/live-hls/', '/live-fhls/');
  3137. url = url.replace('/playlist.m3u8', '/playlist_sfm4s.m3u8');
  3138. return url;
  3139. }
  3140. function clearAllTimeouts() {
  3141. let lastID = setTimeout(function() {}, 0);
  3142. while(lastID--) {
  3143. clearTimeout(lastID);
  3144. }
  3145. }
  3146. function clearAllIntervals() {
  3147. let lastID = setInterval(function() {}, 0);
  3148. while(lastID--) {
  3149. clearInterval(lastID);
  3150. }
  3151. }
  3152. let acre7 = 'tVBC55kUB2OqG1orxk2A';
  3153. function captureStream(el) {
  3154. if(el.captureStream) {
  3155. return el.captureStream();
  3156. }
  3157. if(el.mozCaptureStream) {
  3158. return el.mozCaptureStream();
  3159. }
  3160. return 'err-no-func';
  3161. }
  3162. function clearIntervalEx(intv) {
  3163. if(intv) {
  3164. clearInterval(intv);
  3165. intv = null;
  3166. }
  3167. }
  3168. function clearTimeoutEx(intv) {
  3169. if(intv) {
  3170. clearTimeout(intv);
  3171. intv = null;
  3172. }
  3173. }
  3174. function rev(str) {
  3175. let rv = '';
  3176. for(let i = str.length - 1; i >= 0; i--) {
  3177. rv += str[i];
  3178. }
  3179. return rv;
  3180. }
  3181. let acre8 = '=cCI642ZpFGctF2Y';
  3182. function arraymove(arr, fromIndex, toIndex) {
  3183. let element = arr[fromIndex];
  3184. arr.splice(fromIndex, 1);
  3185. arr.splice(toIndex, 0, element);
  3186. }
  3187. function genRandomStr(len) {
  3188. let res = '';
  3189. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  3190. const charsLen = chars.length;
  3191. let i = 0;
  3192. while(i < len) {
  3193. res += chars.charAt(Math.floor(Math.random() * charsLen));
  3194. i += 1;
  3195. }
  3196. return res;
  3197. }
  3198. function getRandomColor() {
  3199. var letters = '0123456789ABCDEF';
  3200. var color = '#';
  3201. for (var i = 0; i < 6; i++) {
  3202. color += letters[Math.floor(Math.random() * 16)];
  3203. }
  3204. return color;
  3205. }
  3206. function getCSRFToken() {
  3207. let $el = $('input[name="csrfmiddlewaretoken"]');
  3208. if($el.length !== 0) {
  3209. return $el.val();
  3210. }
  3211. }
  3212. function adra($block) {
  3213. $block.addClass('cb-enh-rn-' + genRandomStr(10));
  3214. }
  3215. loadSettings();
  3216. function blocklistGenderApply(gender) {
  3217. let gs = gender;
  3218. if(gender === 't') {
  3219. gs = 's';
  3220. }
  3221. if(gHasHas) {
  3222. let style = `.roomCard:has(span.gender` + gs + `) {display: none !important;}`;
  3223. addStyle(style);
  3224. return;
  3225. }
  3226. $('.roomCard span.gender' + gs).closest('.roomCard').hide();
  3227. }
  3228. if(window.location.pathname === '/' && !isMultiViewPage()) {
  3229. gHiddenGenders = getSetting('featured-hidden-genders');
  3230. if(!gHiddenGenders) {
  3231. gHiddenGenders = [];
  3232. }
  3233. gHiddenGenders.forEach(function(v) {
  3234. blocklistGenderApply(v);
  3235. });
  3236. }
  3237. if(getSetting('reg-redir') === 1) {
  3238. regRedir(getSetting('reg-redir-room'));
  3239. }
  3240. document.addEventListener('click', function(e) {
  3241. if(gRoomMoreMenuVisible && e.target && $(e.target).parent('.cb-enh-room-more-icon').length === 0) {
  3242. setRoomMoreMenuVisible(false);
  3243. }
  3244. if(regRedirPreventClick && e.target !== $('.logo-zone a')[0]) {
  3245. e.stopImmediatePropagation();
  3246. e.preventDefault();
  3247. return;
  3248. }
  3249. if(e.target.nodeName === 'VIDEO' && isVideoPlaying(e.target)) {
  3250. e.stopImmediatePropagation();
  3251. e.preventDefault();
  3252. document.body.click();
  3253. }
  3254. }, true);
  3255. let acre3 = 'ePs287p8MVIhXZizfUzY';
  3256. function loadGridSize() {
  3257. let minGridSize = 50;
  3258. let maxGridSize = 300;
  3259. let size = getSetting('grid-size');
  3260. size = parseInt(size);
  3261. if(Number.isInteger(size)) {
  3262. if(size < minGridSize) {
  3263. setGridSize(minGridSize)
  3264. }
  3265. else if(size > maxGridSize) {
  3266. setGridSize(maxGridSize)
  3267. }
  3268. else {
  3269. setGridSize(size);
  3270. }
  3271. }
  3272. }
  3273. loadGridSize();
  3274. function loadMoreGridSize() {
  3275. let minGridSize = 50;
  3276. let maxGridSize = 300;
  3277. let size = getSetting('grid-more-size');
  3278. size = parseInt(size);
  3279. if(Number.isInteger(size)) {
  3280. if(size < minGridSize) {
  3281. setMoreRoomsGridSize(minGridSize)
  3282. }
  3283. else if(size > maxGridSize) {
  3284. setMoreRoomsGridSize(maxGridSize)
  3285. }
  3286. else {
  3287. setMoreRoomsGridSize(size);
  3288. }
  3289. }
  3290. }
  3291. loadMoreGridSize();
  3292. function fetchRoomSnapshot(username, callback) {
  3293. let req = new XMLHttpRequest();
  3294. req.timeout = 10000;
  3295. req.responseType = 'arraybuffer';
  3296. req.addEventListener('load', function() {
  3297. let length = req.getResponseHeader('Content-length');
  3298. if(length === '4824' || length === '4456') {
  3299. return;
  3300. }
  3301. callback(req.response);
  3302. });
  3303. req.open('GET', 'https://thumb.live.mmcdn.com/riw/' + username + '.jpg');
  3304. req.send();
  3305. }
  3306. function setFollow(username, doFollow, callbackOk, callbackErr) {
  3307. let csrfToken = getCSRFToken();
  3308. if(!csrfToken) {
  3309. return;
  3310. }
  3311. let url = 'https://chaturbate.com/follow/';
  3312. if(doFollow) {
  3313. url += 'follow/';
  3314. }
  3315. else {
  3316. url += 'unfollow/';
  3317. }
  3318. url += username + '/';
  3319. let req = new XMLHttpRequest();
  3320. req.timeout = 10000;
  3321. req.addEventListener('load', function() {
  3322. let resp = JSON.parse(req.response);
  3323. if(!resp) {
  3324. callbackErr();
  3325. return;
  3326. }
  3327. if(!('following' in resp)) {
  3328. callbackErr();
  3329. return;
  3330. }
  3331. callbackOk(resp['following']);
  3332. });
  3333. req.addEventListener('error', function() {
  3334. callbackErr();
  3335. });
  3336. req.open('POST', url);
  3337. req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  3338. req.send('location=FollowStar&csrfmiddlewaretoken=' + csrfToken);
  3339. }
  3340. })();