Sleazy Fork is available in English.

MagicPH

A video downloader for various adult websites.

  1. // ==UserScript==
  2. // @version 4.1.0
  3. // @name MagicPH
  4. // @description A video downloader for various adult websites.
  5. // @author Magic <magicoflolis@tuta.io>
  6. // @supportURL https://github.com/magicoflolis/Magic-PH/issues
  7. // @namespace https://github.com/magicoflolis/Magic-PH
  8. // @homepageURL https://github.com/magicoflolis/Magic-PH
  9. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAADGCAYAAAC5F+58AAANOklEQVR4nO2dL4zcRhuHX3woAYGfHClSSElA6aqRCqpKAVHIgpKAkoBakT6VVK10svRFCskBkxYFGBSeDMpWCigwvC5d6dMBU0uLjKfA8WlvY88fe2besec30sPu1u/OvI89/zxLNF6eE9EVEd0Q0ZGIBABglCN1rnwkopdkUJ4R0XUAXwCAJXNDRI9JUV4TnmYA2OQtjZS3AQQHwBr5QrrHhCcbAC55RiflUwABAbBmbulzeRlAMADEwGuibhqTOxAAYuCaqJu+5A4EgBg4EnV9S+5AAIiBIwUQBAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMcEeAAAxwR4AADHBHgAAMeH/oofDQZiWPM8nXy/Pc+PrHQ4H7oZZLKr2jbxuw2uQoVJV1eTrVVUF4QJq38jrNrwGGSpN00y+Xtu2EC6g9o28bsNrkLGy2WyMr7XdbiddK/KkcNq+kddteA0yVoqiML5WURSTrhV5Ujht38jrNrwGGSv7/d74Wvv9ftK1Ik8Kp+0bed2G1yBjpW1b42tNGb8hKdy2b+R1G16DyMp2u9W+Tpqmk68TeVIAd/i/6BzhyrLUvk5ZlhAOhIb/i6qEa5rGigiy68iuAeGAQ/xfVKePLxNC9zoy2TDOAEz4v6hOsst2h2RZprxGlmWj/19VlVPh0jQVZVmKw+Eg6roe/Py2be++Z1EURmNTXbIsG/yuTdOI/X4viqIQSZJwJ6AxSZKIPM9H27Gv291uJ/I8D+07+r+oTrLL9j/udjvlNXa73ej/53luXbgkSURZlpNnRftrpmk6u36zLFN2mfvStq0oy/JeUqrqxkb7Tvlem81m0jY9Ibqb7JSNE9EIJ9shUte18hpjTxYhuplOm0mx3W61E1ynTN03miTJ5HXHuq7vnrIhCjdnAqwvbdvO2gS/auGI5Gtosm5CkiTSSreZFNvtdtZTbayYSpckifQmo1PattW6GfkULkmSWbPaQ0WnhxSlcLK7texOJRu/9btVbCRFkiRWn2znxWQr21zZ+tK2rfKzfAln4yYyVhifdOEKJ9sHKXsCyPr5fRLbSArZONFG0X1DwnUc58WXcFPHa7qFaUwXrnCynSKyZJQ9dXTHKaqk2Gw28tYU3bhoaJasn8XU6YqqZi914hCie3IVRXEvydI0nZTUPoST9VJOS1VV9yaakiQRWZZpPRmZln7CFY5ofC1NiOE7lCoBbSWFahBf17VyOlrn1SHVEoiOMKpYTMehPoRTddXbtlXO6OrUjY1Z4VUJJ/vboTGObDlB93PP/3ZKQuh2V1SzirKtbLLJob40TaO1DmXy3qBr4XSOxNAVRdVOc04SWKVwsifJ0Os6srvaafLauAunaSqyLBNlWYrdbne3Q8akEVVPSplwNhNTJ5a+uBZOdRMyqV9VHU15A2V1wp2KJBvHDVWWrGt0mnyqRvXVv58jnOo7mB5LoTsenNu+qrpVFZ2dRj19L+BwOIjD4SDKshRlWYo0TTm6k4IYLqhskPMkk5XTSQVVt8gk0V0L12+7Uo2dZMKpuktT1pt0Jhvmtq+sbnVeqeLIWYv4v6ipcLIkOP1b2TLCeSP7FK6fOev3V5oUmXCqYvIk0K0XIdwKx30j9ED4wsnWmU4bQNbFOr/bu2zY7XYriqIQ+/1+9sL4mHA63b8pXSadM2Dmti+E84ypcKo1mf7vZN2z87u97YbdbDZit9tZ33kyJpxO12vKLnkbXbo5wqn+l3lbVhzCqaa/syxTJsp58tkSLkkSpzsi5gg3pW1CF87kjf9ACV84IvkEwW63kwo09HaBDeFsvyUwVCCcXn0siGUIJ3uK1HUt/cyhdZu5ws3ZuFzXtdjtdncTKbKCLqVefSyIZQg35Qc5+jK0M3yucCbdyLqu79Z+TLu2YwmmszNkiZMmERx9sQzhdBdlh8rQNqs5wunEMvQm9RBzFr5VZcqygM6bB3PbV1a3qutDuAlM7TZMedFzbLfFHOF0ngK6Z5TMEW6NC9+q+mDYimWb5Qg35fiAsX13c4RTxW+y1091R5cJp+rWLnFrl84Y0vQ9tqqq7k5qq6pKlGUp8jwXaZpyvBO3HOGm/DDH2FvTLoUzGdjP+Szbm5d1x6UuhSNSd5VN39ZW9QSm/EhMFMJN+empsa5dCMLpPFF8vZ5jMinlWjibm7JdPDGjEY7IbBwn6++7FE43IXT2VarktfECqukMsGvhdOLRfSqpYtE5AS5q4Uw2/8p+3mqOcDpjSdk4zuQ4O9XEh+64q2kakef5vbt5lmWTxsWuhSNSdwOFUHctdTZiMxwmtCzhTM4nlN0FXc9SCtEJf/76kOlhsTrJaePMRpPiQzjdM00Oh4PIsuzuCd6/maFzY57zM9arEk4mislPUMkmDFTSyJIiSRInZ1EOFd0uj63j5LiXBU5xfWoXXkDVrAjdIvsMlbhzDxEyKSp5dSY9bJzhWNe11pPFl3Auz6Vk3CK2POF0uguqRp0rHNH8p0p/9LZqLU53nDHnlOKqqkSSJOx7KX1Ix3Bw0LKF09l+pLqD2RBuTjI0TXM3vlM9VUx/29xknNg0zb0tYKEJd/qd5hb8tsBIUQmn0+1RfYYN4aYk+NgeS9X/m64V9T/pNPTWeV3Xoqqqwb2WqnrR2VrlagPy1F/Pads27l/PWSNJkoweq9AneIC/VfYFSzjiQPX7cP02rv4VqMDqnD0AEBBLEG7hsAcAAkIlnOl4EnwBewBgIv14qz/ktD9qoiiKyQedqsZIK3jjmhv2AMBEdDcBmCwrqCZvmBaL1wR7AGAiOm8L9E9Anc9S7atcwcufIcAeAJiB7jrg+Z7DnjRNRVEUWpuFmReM1wJ7AGAGcw5XMi2BrGMtHfYAwExc7Tc8LZgssQZ7AGAmLjf5CoGupGXYAwAWmLNxWVbwZLMOewDAIrovX8pKYHsP1wZ7AMABm81G5Hl+95t0Mgn7o+LLspx0eCwwgj0AAGKCPQAAYoI9AABigj0AAGKCPQAAYoI9AABiwt2HP7gg8fZbEtdvSNz8SkL8AUB43L7rcvTyxYKFe/4UkoHlcfNrl7uLEu75UxLHK/7KA2AqDqWz+4EPLvBkA8vneLUQ4d5+y19ZANjA0ZjO7gdev+GvKABscP1mAcLdvuOvKABs4KhbafcDuSsJAJtAOAA8AuEA8AiEA8AjEA4Aj0A4ADwC4QDwCIQDwCMQDgCPQDgAPALhPPP+ld36efKIxHdfdbz5hsTvP5D45ze38f398/x6+O6r+Npe/AHhvGNbuDG+Tkj89ZOb+CDcdCCcZ3wJ17P9mkTzwW58EG46EM4zvoUj6p52utJBOLdAOM9wCNdLZys+CDcdCOcZLuGISPzyvZ34INx0IJxnOIUjIvH//0E4CBdRpdtK6H9+6/7u/atuaUC3Pt984yc+FRAOwnnBVUK/+UavPh9e8MR3DoSDcF5wmdC60sk+H8K5BcJ5xmVCNx+6J5jq83//gSe+UyAchPOC64T+5Xv1579/xRdfD4SDcF5wndB//gjhQgbCecZ1Qv/9M4QLGQjnGTzhOiAchPOC64TWmamEcHxAOM+EMEv554888Z0C4SCcF0JYh5Nt7+LeerbmtodwKxGu+aAv25NH8+ODcBBuMdgQrvnQ/c1fP3XrbjrdyB7ZojeEg3DsFcQhnCuePFK/iArhINyq4ExonTNOIByEWxVcCa3z8imEg3DsFbQG4XRlg3AQjr2Clizcwwvzo/KwDucWCOcZH8I9vOiuY3I8nkl8EG46EM4zNoV7eHH/1OX3r+bLAOHcAuE84yuhQ48PwkG4VSV06PFBOAi3qoQOPT4IB+FWldChxwfhINyqEjr0+CAchFtVQoceH4SDcKtK6NDjg3AQblUJHXp8EA7CrSqhQ48PwkG4VSV06PFBOAi3qoQOPT4IB+FWldChxwfhINyqEjr0+CAchFtVQoceH4SDcKtK6NDjg3AQblUJHXp8EA7CAeAcCAeARyAcAB6BcAB4BMIB4BEIB4BHIBwAHoFwAHgEwgHgEQgHgEcgHAAegXAAeCR44W7f8VcSADY4Xi1AuI+v+SsKABt8+u8ChLt8wV9RANjg8sUChHtwgW4lWD6377pcDl44IhLPn/JXGABzePnMiWxuhCMi8ew/eNKB5XG8ciqbO+F6Ll90g8/jFX9lAjDE8arL0csXzrqR/oQDANyDPQAAYoI9AABigj0AAGKCPQAAYoI9AABigj0AAGKCPQAAYoI9AABigj0AAGKCjgEEAUAs0E0AQQAQA7dERB8DCASAGLgmInoZQCAAxMBL+lzQrQTALTdE9IA+l8cBBATAWjkS0XM6K28DCAyANXJJIwXSAWCPI3VO3XUlh8ozwpgOgLncUOeSdnlN3ZLBbQDBAxA6R+pc+USdO4NPtX8BK0q/Y5IfURcAAAAASUVORK5CYII=
  10. // @license MIT
  11. // @compatible chrome
  12. // @compatible firefox
  13. // @compatible edge
  14. // @compatible opera
  15. // @compatible safari
  16. // @connect *
  17. // @grant GM_addElement
  18. // @grant GM_info
  19. // @grant GM_openInTab
  20. // @grant GM_xmlhttpRequest
  21. // @grant GM.addElement
  22. // @grant GM.info
  23. // @grant GM.openInTab
  24. // @grant GM.xmlHttpRequest
  25. // @match https://*.pornhub.com/*
  26. // @match https://*.pornhubpremium.com/*
  27. // @match https://*.youporn.com/*
  28. // @match https://*.youporngay.com/*
  29. // @match https://*.redtube.com/*
  30. // @match https://*.tube8.com/*
  31. // @match https://*.thumbzilla.com/*
  32. // @match https://onlyfans.com/*
  33. // @match https://xhamster.com/*
  34. // @match https://*.xnxx.com/*
  35. // @match https://*.xvideos.com/*
  36. // @match https://beeg.com/*
  37. // @match https://91porn.com/view_video.php?*
  38. // @match https://hqporner.com/hdporn/*
  39. // @match https://spankbang.com/*/video/*
  40. // @match https://*.porntrex.com/video/*/*
  41. // @match https://*.analdin.com/*
  42. // @match https://sxyprn.com/post/*
  43. // @noframes
  44. // @run-at document-start
  45. // ==/UserScript==
  46. (() => {
  47. 'use strict';
  48. const Limit_Downloads = false; // Will apply to OnlyFans only
  49.  
  50. /******************************************************************************/
  51. const inIframe = () => {
  52. try {
  53. return window.self !== window.top;
  54. } catch (e) {
  55. return true;
  56. }
  57. }
  58. if (inIframe()) {
  59. return;
  60. }
  61. let userjs = self.userjs;
  62. /**
  63. * Skip text/plain documents, based on uBlock Origin `vapi.js` file
  64. *
  65. * [Source Code](https://github.com/gorhill/uBlock/blob/master/platform/common/vapi.js)
  66. */
  67. if (
  68. (document instanceof Document ||
  69. (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) &&
  70. /^image\/|^text\/plain/.test(document.contentType || '') === false &&
  71. (self.userjs instanceof Object === false || userjs.UserJS !== true)
  72. ) {
  73. userjs = self.userjs = { UserJS: true };
  74. }
  75. if (!(typeof userjs === 'object' && userjs.UserJS)) {
  76. return;
  77. }
  78. /******************************************************************************/
  79.  
  80. /**
  81. * To compile this CSS `pnpm run build:Sass`
  82. * @desc Link to uncompiled Cascading Style Sheet
  83. * @link https://github.com/magicoflolis/Magic-PH/tree/master/src/sass
  84. */
  85. const mainCSS = `:root {
  86. --mph-site-color: hsl(36, 100%, 50%);
  87. --mph-hover-color: hsl(36, 100%, 35%);
  88. --mph-background-color: hsl(0, 0%, 0%);
  89. --mph-controls-bg-color: hsla(0, 0%, 0%, 0.5);
  90. --mph-border-color: hsl(36, 100%, 50%);
  91. --mph-text-color: hsl(210, 12%, 97%);
  92. --mph-root-bg: hsla(0, 0%, 0%, 0.9);
  93. --mph-header-bg: hsla(0, 0%, 0%, 0);
  94. }
  95.  
  96. .hidden {
  97. display: none !important;
  98. z-index: -1 !important;
  99. }
  100.  
  101. main-userjs {
  102. width: 100%;
  103. width: -moz-available;
  104. width: -webkit-fill-available;
  105. font-family: Arial, Helvetica, sans-serif;
  106. font-size: 14px;
  107. text-rendering: optimizeLegibility;
  108. word-break: normal;
  109. -moz-tab-size: 4;
  110. -o-tab-size: 4;
  111. tab-size: 4;
  112. }
  113. main-userjs.expanded {
  114. height: 100% !important;
  115. bottom: 0rem !important;
  116. }
  117. main-userjs:not(.webext-page) {
  118. height: 492px;
  119. position: fixed;
  120. }
  121. main-userjs:not(.webext-page):not(.expanded) {
  122. margin-left: 1rem;
  123. margin-right: 1rem;
  124. right: 1rem;
  125. bottom: 1rem;
  126. }
  127. main-userjs:not(.webext-page):not(.expanded).auto-height {
  128. height: auto;
  129. }
  130. main-userjs:not(.hidden) {
  131. z-index: 100000000000000000 !important;
  132. display: flex !important;
  133. flex-direction: column !important;
  134. }
  135. main-userjs mph-toolbar {
  136. order: 0;
  137. padding: 0.5em;
  138. display: flex;
  139. place-content: space-between;
  140. }
  141. main-userjs mph-toolbar mph-tabs {
  142. overflow: hidden;
  143. order: 0;
  144. }
  145. main-userjs mph-toolbar mph-column {
  146. flex-flow: row nowrap;
  147. order: 999999999999;
  148. }
  149. main-userjs mph-toolbar > * {
  150. width: -webkit-fit-content;
  151. width: -moz-fit-content;
  152. width: fit-content;
  153. }
  154. main-userjs mph-tabs {
  155. display: flex;
  156. gap: 0.5em;
  157. width: 100%;
  158. width: -moz-available;
  159. width: -webkit-fill-available;
  160. text-align: center;
  161. -webkit-user-select: none;
  162. -moz-user-select: none;
  163. -ms-user-select: none;
  164. user-select: none;
  165. background: var(--mph-header-bg, hsla(0, 0%, 0%, 0));
  166. flex-flow: row wrap;
  167. }
  168. main-userjs mph-tabs mph-tab {
  169. margin: 0.25em;
  170. padding: 0.25em;
  171. min-width: 150px;
  172. width: -webkit-fit-content;
  173. width: -moz-fit-content;
  174. width: fit-content;
  175. height: -webkit-fit-content;
  176. height: -moz-fit-content;
  177. height: fit-content;
  178. display: flex;
  179. place-content: space-between;
  180. border: 1px solid transparent;
  181. border-radius: 4px;
  182. background: transparent;
  183. gap: 0.25em;
  184. }
  185. main-userjs mph-tabs mph-tab.active {
  186. background-color: var(--mph-background-color, hsl(0, 0%, 0%));
  187. }
  188. main-userjs mph-tabs mph-tab:not(.active):hover {
  189. background-color: var(--mph-background-color, hsl(0, 0%, 0%));
  190. }
  191. main-userjs mph-tabs mph-tab mph-host {
  192. float: left;
  193. overflow: auto;
  194. overflow-wrap: break-word;
  195. text-overflow: ellipsis;
  196. white-space: nowrap;
  197. }
  198. main-userjs mph-tabs mph-tab mph-elem {
  199. float: right;
  200. }
  201. main-userjs mph-tabs mph-addtab {
  202. order: 999999999999;
  203. margin: 0.25em;
  204. font-size: 20px;
  205. padding: 0px 0.25em;
  206. }
  207. main-userjs mph-tabs mph-addtab:hover {
  208. background-color: var(--mph-background-color, hsl(0, 0%, 0%));
  209. }
  210. main-userjs input {
  211. width: -webkit-fit-content;
  212. width: -moz-fit-content;
  213. width: fit-content;
  214. height: -webkit-fit-content;
  215. height: -moz-fit-content;
  216. height: fit-content;
  217. background: hsla(0, 0%, 0%, 0);
  218. color: var(--mph-text-color, hsl(210, 12%, 97%));
  219. }
  220. main-userjs input:not([type=checkbox]) {
  221. border: transparent;
  222. outline: none !important;
  223. }
  224. @media screen and (max-height: 720px) {
  225. main-userjs:not(.webext-page) {
  226. height: 100% !important;
  227. bottom: 0rem !important;
  228. right: 0rem !important;
  229. margin: 0rem !important;
  230. }
  231. }
  232.  
  233. .mgp_download {
  234. color: var(--mph-text-color, hsl(210, 12%, 97%));
  235. }
  236.  
  237. .mph_progressContainer {
  238. width: -webkit-fit-content;
  239. width: -moz-fit-content;
  240. width: fit-content;
  241. height: auto;
  242. color: var(--mph-site-color, hsl(36, 100%, 50%));
  243. background-color: var(--mph-background-color, hsl(0, 0%, 0%));
  244. border: 1px solid var(--mph-border-color, hsl(36, 100%, 50%));
  245. margin: 0 3px;
  246. border-radius: 1000px;
  247. padding: 14px 16px;
  248. position: fixed;
  249. display: none;
  250. top: 10em;
  251. left: 0.5em;
  252. z-index: 50000;
  253. font-size: 14px;
  254. font-weight: 500;
  255. font-family: Arial, Helvetica, sans-serif;
  256. line-height: 20px;
  257. text-align: center;
  258. }
  259. .mph_progressContainer .mph_progress {
  260. font-size: 16px;
  261. font-weight: 700;
  262. margin: auto;
  263. color: currentcolor !important;
  264. }
  265.  
  266. .mgp_download .magicph-icon {
  267. height: 1.25em;
  268. }
  269.  
  270. .mgp_videoStarted.mgp_hideControls .mgp_download {
  271. display: none;
  272. }
  273.  
  274. .mgp_contextMenu > .mgp_content > .mgp_download {
  275. border-bottom: 1px solid hsla(0, 0%, 100%, 0.2) !important;
  276. }
  277.  
  278. ul.mgp_switches > .mgp_download {
  279. display: grid !important;
  280. }
  281. ul.mgp_switches > .mgp_download .magicph-icon {
  282. display: block;
  283. font-size: 25px;
  284. min-height: 36px;
  285. left: 50%;
  286. padding-bottom: 5px;
  287. position: relative;
  288. top: 0px;
  289. transform: translate(-50%, 0%);
  290. }
  291.  
  292. .xp-context-menu .mgp_download {
  293. color: hsl(0, 0%, 63%);
  294. cursor: pointer;
  295. font-family: ArialMT, sans-serif;
  296. font-size: 14px;
  297. line-height: 2.14;
  298. padding: 0 20px;
  299. }
  300. .xp-context-menu .mgp_download:hover {
  301. background: hsl(0, 0%, 26%);
  302. color: hsl(0, 0%, 100%);
  303. }
  304.  
  305. .mgp_controls > .mgp_download {
  306. width: -webkit-fit-content;
  307. width: -moz-fit-content;
  308. width: fit-content;
  309. height: -webkit-min-content;
  310. height: -moz-min-content;
  311. height: min-content;
  312. cursor: pointer;
  313. position: absolute;
  314. top: 50%;
  315. bottom: 0;
  316. left: 80%;
  317. right: 0;
  318. z-index: 150;
  319. border-radius: 5px;
  320. background-color: hsla(0, 0%, 0%, 0.7);
  321. padding: 8px 10px;
  322. }
  323.  
  324. mph-root,
  325. .mph_of_root {
  326. height: 100%;
  327. width: 100%;
  328. display: flex;
  329. flex-flow: column nowrap;
  330. border-radius: 10px;
  331. }
  332.  
  333. mph-root {
  334. color: var(--mph-text-color, hsl(210, 12%, 97%));
  335. background: var(--mph-root-bg, hsla(0, 0%, 0%, 0.9));
  336. border: 1px solid hsla(211, 12%, 59%, 0.25);
  337. }
  338. mph-root .mph_list_header {
  339. order: 0;
  340. display: flex;
  341. background: var(--mph-header-bg, hsla(0, 0%, 0%, 0));
  342. border-top-left-radius: 10px;
  343. border-top-right-radius: 10px;
  344. place-content: space-between;
  345. height: fit-content;
  346. height: -moz-fit-content;
  347. height: -webkit-fit-content;
  348. width: 100%;
  349. width: -moz-available;
  350. width: -webkit-fill-available;
  351. -webkit-user-select: none;
  352. -moz-user-select: none;
  353. -ms-user-select: none;
  354. user-select: none;
  355. padding: 0.5em 0.5em 0 0.5em;
  356. font-size: 1.6rem;
  357. }
  358. mph-root .mph_list_header .mgp_title {
  359. order: 0;
  360. cursor: default !important;
  361. float: left !important;
  362. }
  363. mph-root .mph_list_header mph-close {
  364. order: 99999;
  365. cursor: pointer !important;
  366. float: right !important;
  367. border: 0 !important;
  368. }
  369. mph-root .mph_of_header {
  370. gap: 1em;
  371. justify-content: center;
  372. display: flex;
  373. width: -webkit-fit-content;
  374. width: -moz-fit-content;
  375. width: fit-content;
  376. height: -webkit-fit-content;
  377. height: -moz-fit-content;
  378. height: fit-content;
  379. -webkit-user-select: none;
  380. -moz-user-select: none;
  381. -ms-user-select: none;
  382. user-select: none;
  383. }
  384. mph-root mph-a:not(.self-container) {
  385. cursor: pointer;
  386. -webkit-user-select: none;
  387. -moz-user-select: none;
  388. -ms-user-select: none;
  389. user-select: none;
  390. }
  391. mph-root mph-a svg {
  392. fill: currentColor;
  393. width: 12px;
  394. height: 12px;
  395. background: transparent;
  396. }
  397. mph-root mph-a > img {
  398. cursor: pointer;
  399. max-width: 25rem;
  400. max-height: 15rem;
  401. display: block;
  402. margin: auto;
  403. }
  404. mph-root mph-list {
  405. order: 1;
  406. width: 100%;
  407. display: flex;
  408. flex-flow: row wrap;
  409. overflow-x: hidden;
  410. }
  411. mph-root mph-list.mph-list {
  412. padding: 0px;
  413. flex-flow: column nowrap;
  414. gap: 0.5em;
  415. }
  416. mph-root mph-list.mph-list .mph-item {
  417. display: flex;
  418. flex-flow: row wrap;
  419. width: 100%;
  420. align-items: center;
  421. border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  422. padding-top: 0.5em;
  423. padding-bottom: 0.5em;
  424. justify-content: space-evenly;
  425. }
  426. mph-root mph-list.mph-list .mph-item label > input {
  427. outline: 0 none;
  428. background: transparent;
  429. border: 0;
  430. -webkit-user-select: all !important;
  431. -moz-user-select: all !important;
  432. -ms-user-select: all !important;
  433. user-select: all !important;
  434. }
  435. mph-root mph-list.mph-list .mph-item mph-page {
  436. width: 100%;
  437. width: -moz-available;
  438. width: -webkit-fill-available;
  439. justify-content: center;
  440. display: grid;
  441. overflow-y: auto;
  442. }
  443. mph-root mph-list.mph_of_list {
  444. height: 100%;
  445. flex-flow: row wrap;
  446. gap: 1em;
  447. margin: auto;
  448. touch-action: auto;
  449. padding: 0.5em;
  450. }
  451. mph-root mph-list.mph_of_list.mph_mobile {
  452. flex-flow: row wrap;
  453. justify-content: center;
  454. }
  455. mph-root mph-list.mph_of_list .wrap {
  456. display: flex;
  457. flex-flow: column wrap;
  458. width: -webkit-fit-content;
  459. width: -moz-fit-content;
  460. width: fit-content;
  461. height: -webkit-fit-content;
  462. height: -moz-fit-content;
  463. height: fit-content;
  464. border: 1px solid rgba(138, 150, 163, 0.25);
  465. border-radius: 10px;
  466. background-color: var(--overlay-color, rgba(0, 0, 0, 0.6));
  467. padding: 0.5em;
  468. gap: 0.5em;
  469. margin: auto;
  470. }
  471. mph-root mph-list.mph_of_list .wrap mph-a {
  472. order: 1;
  473. }
  474. mph-root mph-list.mph_of_list .wrap mph-title {
  475. order: 2;
  476. }
  477. mph-root mph-list.mph_of_list .wrap mph-title > mph-a,
  478. mph-root mph-list.mph_of_list .wrap mph-title {
  479. font-size: 17px;
  480. font-weight: 700;
  481. color: var(--mph-text-color, hsl(210, 12%, 97%));
  482. word-break: break-word;
  483. word-wrap: break-word;
  484. font-family: Arial, Helvetica, sans-serif;
  485. outline-style: none;
  486. text-decoration: none;
  487. text-align: center;
  488. }
  489. mph-root mph-list.mph_of_list .wrap mph-title > mph-a:hover,
  490. mph-root mph-list.mph_of_list .wrap mph-title:hover {
  491. text-decoration: underline;
  492. }
  493. mph-root mph-list.mph_of_list .wrap .btn-container {
  494. order: 0;
  495. display: flex;
  496. gap: 0.5em;
  497. justify-content: center;
  498. }
  499. mph-root mph-list.mph_of_list .wrap video {
  500. order: 3;
  501. width: 10em;
  502. height: 10em;
  503. }
  504. mph-root mph-list.mph_of_list .wrap .more-info {
  505. order: 99999;
  506. cursor: default;
  507. -webkit-user-select: none;
  508. -moz-user-select: none;
  509. -ms-user-select: none;
  510. user-select: none;
  511. width: 100%;
  512. height: -webkit-fit-content;
  513. height: -moz-fit-content;
  514. height: fit-content;
  515. text-align: center;
  516. border-radius: 10px;
  517. color: var(--mph-site-color, hsl(36, 100%, 50%));
  518. border: 1px solid var(--mph-border-color, hsl(36, 100%, 50%));
  519. padding: 0 0.5em;
  520. -webkit-user-select: none;
  521. -moz-user-select: none;
  522. -ms-user-select: none;
  523. user-select: none;
  524. background-color: hsla(211, 12%, 59%, 0.05);
  525. }
  526. mph-root mph-list mph-a {
  527. -webkit-text-decoration: 0;
  528. text-decoration: 0;
  529. color: var(--mph-site-color, var(--mph-text-color, hsl(36, 100%, 50%)));
  530. }
  531. mph-root mph-list mph-a:hover {
  532. color: var(--mph-hover-color, hsl(36, 100%, 50%));
  533. -webkit-text-decoration: 0;
  534. text-decoration: 0;
  535. }
  536. mph-root > video {
  537. order: 2;
  538. padding: 1em;
  539. }
  540.  
  541. mph-btn {
  542. cursor: pointer;
  543. -webkit-user-select: none;
  544. -moz-user-select: none;
  545. -ms-user-select: none;
  546. user-select: none;
  547. width: -webkit-fit-content;
  548. width: -moz-fit-content;
  549. width: fit-content;
  550. height: -webkit-fit-content;
  551. height: -moz-fit-content;
  552. height: fit-content;
  553. text-align: center;
  554. border-radius: 10px;
  555. }
  556. mph-btn:not(.of_btn) {
  557. color: var(--mph-text-color, hsl(210, 12%, 97%));
  558. background-color: var(--overlay-color, rgba(0, 0, 0, 0.6));
  559. border: 1px solid rgba(138, 150, 163, 0.25);
  560. font-family: Arial, Helvetica, sans-serif;
  561. font-size: 14px;
  562. padding: 0.1em 0.5em;
  563. }
  564. mph-btn.of_btn {
  565. color: var(--mph-site-color, hsl(36, 100%, 50%));
  566. border: 1px solid var(--mph-border-color, hsl(36, 100%, 50%));
  567. padding: 0 0.5em;
  568. -webkit-user-select: none;
  569. -moz-user-select: none;
  570. -ms-user-select: none;
  571. user-select: none;
  572. background-color: var(--mph-background-color, hsl(0, 0%, 0%));
  573. }
  574. mph-btn.of_btn[data-command=toggle-list] {
  575. background-color: var(--bg-color, hsl(240, 4%, 9%));
  576. }
  577. mph-btn svg {
  578. width: 12px;
  579. height: 12px;
  580. fill: currentColor;
  581. background: transparent;
  582. }
  583.  
  584. mph-controls {
  585. display: flex;
  586. margin: auto;
  587. position: fixed;
  588. background-color: transparent;
  589. border: transparent;
  590. color: inherit;
  591. justify-content: center;
  592. gap: 1em;
  593. padding: 0.3em 0.5em;
  594. z-index: 50000;
  595. box-sizing: border-box;
  596. font: 16px/1.3334 Roboto, sans-serif;
  597. }
  598. mph-controls:not(.mph_mobile) {
  599. bottom: 1em;
  600. right: 1em;
  601. }
  602. mph-controls.mph_mobile {
  603. top: 1em;
  604. left: 1em;
  605. }
  606. mph-controls .mph_overlay {
  607. position: absolute;
  608. left: 0;
  609. top: 0;
  610. width: 100%;
  611. height: 100%;
  612. will-change: opacity;
  613. transition: none;
  614. background: hsl(0, 0%, 0%);
  615. opacity: 0.4;
  616. transform: translateZ(0);
  617. -webkit-backface-visibility: hidden;
  618. border: 1px solid hsla(211, 12%, 59%, 0.25);
  619. border-radius: 10px;
  620. z-index: -1;
  621. }
  622. `;
  623. /**
  624. * Link to uncompressed locales + compiler
  625. * @link https://github.com/magicoflolis/Magic-PH/tree/master/src/_locales
  626. * @link https://github.com/magicoflolis/Magic-PH/blob/master/tools/languageLoader.js
  627. */
  628. const translations = {
  629. "en": {
  630. "newTab": "New Tab",
  631. "copy": "Copy",
  632. "no_license": "N/A",
  633. "close": "Close",
  634. "download": "Download",
  635. "remove": "Remove"
  636. }
  637. };
  638. // #region Console Logs
  639. const dbg = (...msg) => {
  640. const dt = new Date();
  641. console.debug(
  642. '[%cMagicPH%c] %cDBG',
  643. 'color: rgb(255,153,0);',
  644. '',
  645. 'color: rgb(255, 212, 0);',
  646. `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`,
  647. ...msg
  648. );
  649. };
  650. const err = (...msg) => {
  651. console.error(
  652. '[%cMagicPH%c] %cERROR',
  653. 'color: rgb(255,153,0);',
  654. '',
  655. 'color: rgb(249, 24, 128);',
  656. ...msg
  657. );
  658. const a = typeof alert !== 'undefined' && alert;
  659. for (const ex of msg) {
  660. if (typeof ex === 'object' && 'cause' in ex && a) {
  661. a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`);
  662. }
  663. }
  664. };
  665. const info = (...msg) => {
  666. console.info(
  667. '[%cMagicPH%c] %cINF',
  668. 'color: rgb(255,153,0);',
  669. '',
  670. 'color: rgb(0, 186, 124);',
  671. ...msg
  672. );
  673. };
  674.  
  675. const log = (...msg) => {
  676. console.log(
  677. '[%cMagicPH%c] %cLOG',
  678. 'color: rgb(255,153,0);',
  679. '',
  680. 'color: rgb(255, 212, 0);',
  681. ...msg
  682. );
  683. };
  684. // #endregion
  685.  
  686. /**
  687. * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js
  688. * @returns {typeof globalThis}
  689. */
  690. function globalWin() {
  691. const check = function (it) {
  692. return it && it.Math === Math && it;
  693. };
  694. return (
  695. check(typeof globalThis == 'object' && globalThis) ||
  696. check(typeof window == 'object' && window) ||
  697. check(typeof self == 'object' && self) ||
  698. check(typeof this == 'object' && this) ||
  699. (function () {
  700. return this;
  701. })() ||
  702. Function('return this')()
  703. );
  704. }
  705.  
  706. /** @type { import("../typings/UserJS.d.ts").safeSelf } */
  707. function safeSelf() {
  708. if (userjs.safeSelf) {
  709. return userjs.safeSelf;
  710. }
  711. const g = globalWin();
  712. const safe = {
  713. XMLHttpRequest: g.XMLHttpRequest,
  714. createElement: g.document.createElement.bind(g.document),
  715. createElementNS: g.document.createElementNS.bind(g.document),
  716. createTextNode: g.document.createTextNode.bind(g.document),
  717. navigator: g.navigator
  718. };
  719. userjs.safeSelf = safe;
  720. return userjs.safeSelf;
  721. }
  722.  
  723. let currentUserId;
  724. let tsSrc;
  725. let vueRouter = [];
  726.  
  727. const getUAData = () => {
  728. if (userjs.isMobile !== undefined) {
  729. return userjs.isMobile;
  730. }
  731. try {
  732. const { navigator } = safeSelf();
  733. if (navigator) {
  734. const { userAgent, userAgentData } = navigator;
  735. const { platform, mobile } = userAgentData ? Object(userAgentData) : {};
  736. userjs.isMobile =
  737. /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') ||
  738. Boolean(mobile) ||
  739. /Android|Apple/.test(platform ? String(platform) : '');
  740. } else {
  741. userjs.isMobile = false;
  742. }
  743. } catch (ex) {
  744. userjs.isMobile = false;
  745. ex.cause = 'getUAData';
  746. err(ex);
  747. }
  748. return userjs.isMobile;
  749. };
  750. const isMobile = getUAData();
  751. const BLANK_PAGE = 'about:blank';
  752. const isGM = typeof GM !== 'undefined';
  753. const win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
  754.  
  755. // #region i18n
  756. class i18nHandler {
  757. constructor() {
  758. if (userjs.pool !== undefined) {
  759. return this;
  760. }
  761. userjs.pool = new Map();
  762. for (const [k, v] of Object.entries(translations)) {
  763. if (!userjs.pool.has(k)) userjs.pool.set(k, v);
  764. }
  765. }
  766. /**
  767. * @param {string | Date | number} str
  768. */
  769. toDate(str = '') {
  770. const { navigator } = safeSelf();
  771. return new Intl.DateTimeFormat(navigator.language).format(
  772. typeof str === 'string' ? new Date(str) : str
  773. );
  774. }
  775. /**
  776. * @param {number | bigint} number
  777. */
  778. toNumber(number) {
  779. const { navigator } = safeSelf();
  780. return new Intl.NumberFormat(navigator.language).format(number);
  781. }
  782. /**
  783. * @type { import("../typings/UserJS.d.ts").i18n$ }
  784. */
  785. i18n$(key) {
  786. const { navigator } = safeSelf();
  787. const current = navigator.language.split('-')[0] ?? 'en';
  788. return userjs.pool.get(current)?.[key] ?? 'Invalid Key';
  789. }
  790. }
  791. const language = new i18nHandler();
  792. const { i18n$ } = language;
  793. // #endregion
  794. // #region Utilities
  795. /**
  796. * @type { import("../typings/types.d.ts").qs }
  797. */
  798. const qs = (selector, root) => {
  799. try {
  800. return (root || document).querySelector(selector);
  801. } catch (ex) {
  802. err(ex);
  803. }
  804. return null;
  805. };
  806. /**
  807. * @type { import("../typings/types.d.ts").qsA }
  808. */
  809. const qsA = (selectors, root) => {
  810. try {
  811. return (root || document).querySelectorAll(selectors);
  812. } catch (ex) {
  813. err(ex);
  814. }
  815. return [];
  816. };
  817. /**
  818. * @type { import("../typings/types.d.ts").query }
  819. */
  820. const query = async (selector, root) => {
  821. try {
  822. while (isNull((root || document).querySelector(selector))) {
  823. await new Promise((resolve) => requestAnimationFrame(resolve));
  824. }
  825. return (root || document).querySelector(selector);
  826. } catch (ex) {
  827. err(ex);
  828. }
  829. return root;
  830. };
  831. /**
  832. * @type { import("../typings/types.d.ts").objToStr }
  833. */
  834. const objToStr = (obj) => Object.prototype.toString.call(obj);
  835. /**
  836. * @type { import("../typings/types.d.ts").strToURL }
  837. */
  838. const strToURL = (str) => {
  839. const WIN_LOCATION = window.location ?? BLANK_PAGE;
  840. try {
  841. str = str ?? WIN_LOCATION;
  842. return objToStr(str).includes('URL') ? str : new URL(str);
  843. } catch (ex) {
  844. ex.cause = 'strToURL';
  845. this.showError(ex);
  846. }
  847. return WIN_LOCATION;
  848. };
  849. /**
  850. * @type { import("../typings/types.d.ts").isRegExp }
  851. */
  852. const isRegExp = (obj) => {
  853. const s = objToStr(obj);
  854. return s.includes('RegExp');
  855. };
  856. /**
  857. * @type { import("../typings/types.d.ts").isElem }
  858. */
  859. const isElem = (obj) => {
  860. const s = objToStr(obj);
  861. return s.includes('Element');
  862. };
  863. /**
  864. * @type { import("../typings/types.d.ts").isObj }
  865. */
  866. const isObj = (obj) => {
  867. const s = objToStr(obj);
  868. return s.includes('Object');
  869. };
  870. /**
  871. * @type { import("../typings/types.d.ts").isFN }
  872. */
  873. const isFN = (obj) => {
  874. const s = objToStr(obj);
  875. return s.includes('Function');
  876. };
  877. /**
  878. * @type { import("../typings/types.d.ts").isNull }
  879. */
  880. const isNull = (obj) => {
  881. return Object.is(obj, null) || Object.is(obj, undefined);
  882. };
  883. /**
  884. * @type { import("../typings/types.d.ts").isBlank }
  885. */
  886. const isBlank = (obj) => {
  887. return (
  888. (typeof obj === 'string' && Object.is(obj.trim(), '')) ||
  889. ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) ||
  890. (Array.isArray(obj) && Object.is(obj.length, 0)) ||
  891. (isObj(obj) && Object.is(Object.keys(obj).length, 0))
  892. );
  893. };
  894. /**
  895. * @type { import("../typings/types.d.ts").isEmpty }
  896. */
  897. const isEmpty = (obj) => {
  898. return isNull(obj) || isBlank(obj);
  899. };
  900. /**
  901. * @type { import("../typings/types.d.ts").normalizeTarget }
  902. */
  903. const normalizeTarget = (target, toQuery = true, root) => {
  904. if (Object.is(target, null) || Object.is(target, undefined)) {
  905. return [];
  906. }
  907. if (Array.isArray(target)) {
  908. return target;
  909. }
  910. if (typeof target === 'string') {
  911. return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target];
  912. }
  913. if (isElem(target)) {
  914. return [target];
  915. }
  916. return Array.from(target);
  917. };
  918. /**
  919. * @type { import("../typings/types.d.ts").ael }
  920. */
  921. const ael = (el, type, listener, options = {}) => {
  922. try {
  923. for (const elem of normalizeTarget(el)) {
  924. if (!elem) {
  925. continue;
  926. }
  927. if (isMobile && type === 'click') {
  928. elem.addEventListener('touchstart', listener, options);
  929. continue;
  930. }
  931. elem.addEventListener(type, listener, options);
  932. }
  933. } catch (ex) {
  934. err(ex);
  935. }
  936. };
  937. /**
  938. * @type { import("../typings/types.d.ts").formAttrs }
  939. */
  940. const formAttrs = (elem, attr = {}) => {
  941. if (!elem) {
  942. return elem;
  943. }
  944. for (const key in attr) {
  945. if (typeof attr[key] === 'object') {
  946. formAttrs(elem[key], attr[key]);
  947. } else if (isFN(attr[key])) {
  948. if (/^on/.test(key)) {
  949. elem[key] = attr[key];
  950. continue;
  951. }
  952. ael(elem, key, attr[key]);
  953. } else if (key === 'class') {
  954. elem.className = attr[key];
  955. } else {
  956. elem[key] = attr[key];
  957. }
  958. }
  959. return elem;
  960. };
  961. /**
  962. * @type { import("../typings/types.d.ts").make }
  963. */
  964. const make = (tagName, cname, attrs) => {
  965. let el;
  966. try {
  967. const { createElement } = safeSelf();
  968. el = createElement(tagName);
  969. if (!isEmpty(cname)) {
  970. if (typeof cname === 'string') {
  971. el.className = cname;
  972. } else if (isObj(cname)) {
  973. formAttrs(el, cname);
  974. }
  975. }
  976. if (!isEmpty(attrs)) {
  977. if (typeof attrs === 'string') {
  978. el.textContent = attrs;
  979. } else if (isObj(attrs)) {
  980. formAttrs(el, attrs);
  981. }
  982. }
  983. } catch (ex) {
  984. ex.cause = 'make';
  985. err(ex);
  986. }
  987. return el;
  988. };
  989.  
  990. /**
  991. * @type { import("../typings/UserJS.d.ts").getGMInfo }
  992. */
  993. const getGMInfo = () => {
  994. if (isGM) {
  995. if (isObj(GM.info)) {
  996. return GM.info;
  997. } else if (isObj(GM_info)) {
  998. return GM_info;
  999. }
  1000. }
  1001. return {
  1002. script: {
  1003. icon: '',
  1004. name: 'MagicPH',
  1005. namespace: 'https://github.com/magicoflolis/MagicPH',
  1006. updateURL: 'https://github.com/magicoflolis/Magic-PH/raw/master/dist/magicph.user.js',
  1007. version: 'Bookmarklet',
  1008. bugs: 'https://github.com/magicoflolis/Magic-PH/issues'
  1009. }
  1010. };
  1011. };
  1012. const $info = getGMInfo();
  1013. /**
  1014. * @type { import("../typings/UserJS.d.ts").loadCSS }
  1015. */
  1016. const loadCSS = (css, name = 'CSS') => {
  1017. try {
  1018. if (typeof name !== 'string') {
  1019. throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' });
  1020. }
  1021. const parent = document.documentElement || document.head || document.body;
  1022. if (qs(`style[data-role="${name}"]`, parent)) {
  1023. return qs(`style[data-role="${name}"]`, parent);
  1024. }
  1025. if (typeof css !== 'string') {
  1026. throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' });
  1027. }
  1028. if (isBlank(css)) {
  1029. throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' });
  1030. }
  1031. if (isGM) {
  1032. let sty;
  1033. if (isFN(GM.addElement)) {
  1034. sty = GM.addElement(parent, 'style', {
  1035. textContent: css
  1036. });
  1037. } else if (isFN(GM_addElement)) {
  1038. sty = GM_addElement(parent, 'style', {
  1039. textContent: css
  1040. });
  1041. }
  1042. if (isElem(sty)) {
  1043. sty.dataset.insertedBy = $info.script.name;
  1044. sty.dataset.role = name;
  1045. return sty;
  1046. }
  1047. }
  1048. const sty = make('style', {
  1049. textContent: css,
  1050. dataset: {
  1051. insertedBy: $info.script.name,
  1052. role: name
  1053. }
  1054. });
  1055. parent.appendChild(sty);
  1056. return sty;
  1057. } catch (ex) {
  1058. err(ex);
  1059. }
  1060. };
  1061. const delay = (timeout = 5000) => new Promise((resolve) => setTimeout(resolve, timeout));
  1062. /**
  1063. * @type { import("../typings/UserJS.d.ts").observe }
  1064. */
  1065. const observe = (element, listener, options = { subtree: true, childList: true }) => {
  1066. const observer = new MutationObserver(listener);
  1067. observer.observe(element, options);
  1068. listener.call(element, [], observer);
  1069. return observer;
  1070. };
  1071. const smToArr = (m) => {
  1072. let arr = [];
  1073. if (objToStr(m).includes('Map')) {
  1074. for (const [k, v] of m) {
  1075. arr.push([k, v]);
  1076. }
  1077. } else if (objToStr(m).includes('Set')) {
  1078. arr.push(...[...m]);
  1079. } else if (Array.isArray(m)) {
  1080. arr = m;
  1081. } else {
  1082. arr = normalizeTarget(m);
  1083. }
  1084. return arr;
  1085. };
  1086. const fancyTimeFormat = (duration) => {
  1087. // Hours, minutes and seconds
  1088. const hrs = ~~(duration / 3600);
  1089. const mins = ~~((duration % 3600) / 60);
  1090. const secs = ~~duration % 60;
  1091.  
  1092. // Output like "1:01" or "4:03:59" or "123:03:59"
  1093. let ret = '';
  1094.  
  1095. if (hrs > 0) {
  1096. ret += '' + hrs + ':' + (mins < 10 ? '0' : '');
  1097. }
  1098.  
  1099. ret += '' + mins + ':' + (secs < 10 ? '0' : '');
  1100. ret += '' + secs;
  1101.  
  1102. return ret;
  1103. };
  1104. // #endregion
  1105.  
  1106. // #region Classes
  1107. /**
  1108. * @type { import("../typings/types.d.ts").dom }
  1109. */
  1110. const dom = {
  1111. attr(target, attr, value = undefined) {
  1112. for (const elem of normalizeTarget(target)) {
  1113. if (value === undefined) {
  1114. return elem.getAttribute(attr);
  1115. }
  1116. if (value === null) {
  1117. elem.removeAttribute(attr);
  1118. } else {
  1119. elem.setAttribute(attr, value);
  1120. }
  1121. }
  1122. },
  1123. prop(target, prop, value = undefined) {
  1124. for (const elem of normalizeTarget(target)) {
  1125. if (value === undefined) {
  1126. return elem[prop];
  1127. }
  1128. elem[prop] = value;
  1129. }
  1130. },
  1131. text(target, text) {
  1132. const targets = normalizeTarget(target);
  1133. if (text === undefined) {
  1134. return targets.length !== 0 ? targets[0].textContent : undefined;
  1135. }
  1136. for (const elem of targets) {
  1137. elem.textContent = text;
  1138. }
  1139. },
  1140. remove(target) {
  1141. return normalizeTarget(target).some((elem) => elem.remove());
  1142. },
  1143. cl: {
  1144. add(target, token) {
  1145. token = Array.isArray(token) ? token : [token];
  1146. return normalizeTarget(target).some((elem) => elem.classList.add(...token));
  1147. },
  1148. remove(target, token) {
  1149. token = Array.isArray(token) ? token : [token];
  1150. return normalizeTarget(target).some((elem) => elem.classList.remove(...token));
  1151. },
  1152. toggle(target, token, force) {
  1153. let r;
  1154. for (const elem of normalizeTarget(target)) {
  1155. r = elem.classList.toggle(token, force);
  1156. }
  1157. return r;
  1158. },
  1159. has(target, token) {
  1160. return normalizeTarget(target).some((elem) => elem.classList.contains(token));
  1161. }
  1162. }
  1163. };
  1164.  
  1165. // const urlContainer = make('mph-url');
  1166. // const urlBar = make('input', 'mph-url-bar', {
  1167. // autocomplete: 'off',
  1168. // spellcheck: false,
  1169. // type: 'text',
  1170. // placeholder: 'Placeholder text'
  1171. // });
  1172.  
  1173. // #region Handle Page
  1174. class HandlePage {
  1175. constructor(url) {
  1176. this.remove = this.remove.bind(this);
  1177. this.hosts = {
  1178. // 'about:blank': {
  1179. // domains: [],
  1180. // ...HandlePage.domainDefaults
  1181. // },
  1182. pornhub: {
  1183. domains: ['pornhub.com', 'pornhubpremium.com']
  1184. },
  1185. youporn: {
  1186. domains: ['youporn.com', 'youporngay.com']
  1187. },
  1188. redtube: {
  1189. domains: ['redtube.com']
  1190. },
  1191. tube8: {
  1192. domains: ['tube8.com']
  1193. },
  1194. thumbzilla: {
  1195. domains: ['thumbzilla.com']
  1196. },
  1197. onlyfans: {
  1198. domains: ['onlyfans.com']
  1199. },
  1200. xhamster: {
  1201. domains: ['xhamster.com']
  1202. },
  1203. xnxx: {
  1204. domains: ['xnxx.com']
  1205. },
  1206. xvideos: {
  1207. domains: ['xvideos.com']
  1208. },
  1209. beeg: {
  1210. domains: ['beeg.com']
  1211. },
  1212. '91porn': {
  1213. domains: ['91porn.com']
  1214. },
  1215. hqporner: {
  1216. domains: ['hqporner.com']
  1217. },
  1218. spankbang: {
  1219. domains: ['spankbang.com']
  1220. },
  1221. porntrex: {
  1222. domains: ['porntrex.com']
  1223. },
  1224. analdin: {
  1225. domains: ['analdin.com']
  1226. },
  1227. porn00: {
  1228. domains: ['porn00.org']
  1229. },
  1230. sxyprn: {
  1231. domains: ['sxyprn.com']
  1232. }
  1233. };
  1234. this.videoData = {};
  1235. this.cache = {
  1236. domain: 'blank'
  1237. };
  1238. this.current = url;
  1239. this.theme = undefined;
  1240. if (isEmpty(url) || !isNull(this.theme)) {
  1241. this.theme = this.themeHandler().load();
  1242. }
  1243. this.supported = isFN(make('main-userjs').attachShadow);
  1244. this.frame = make('main-userjs', 'hidden', {
  1245. dataset: {
  1246. insertedBy: $info.script.name,
  1247. role: 'primary-container'
  1248. }
  1249. });
  1250. this.injected = false;
  1251.  
  1252. window.addEventListener('beforeunload', this.remove);
  1253. }
  1254. /**
  1255. * @param { function(): * } callback
  1256. * @param { Document } doc
  1257. */
  1258. inject(callback, doc) {
  1259. if (!doc) {
  1260. return;
  1261. }
  1262. try {
  1263. if (this.injected || !isNull(qs('main-userjs'))) {
  1264. return;
  1265. }
  1266. info({ Site: this.webpage.origin, isMobile, validDomain: this.cache.validDomain });
  1267.  
  1268. if (isMobile) {
  1269. dom.cl.add([this.frame, mphControls, dul], 'mph_mobile');
  1270. // Prevents being redirected to "Continue to video"
  1271. if (this.host.includes('pornhub')) {
  1272. const makeCookie = (name, value, options = {}) => {
  1273. try {
  1274. Object.assign(options, {
  1275. path: '/'
  1276. });
  1277. if (options.expires instanceof Date) {
  1278. options.expires = options.expires.toUTCString();
  1279. }
  1280. let updatedCookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
  1281. for (const key in options) {
  1282. updatedCookie += `; ${key}`;
  1283. const optionValue = options[key];
  1284. if (optionValue !== true) {
  1285. updatedCookie += `=${optionValue}`;
  1286. }
  1287. }
  1288. document.cookie = updatedCookie;
  1289. info('[makeCookie] New cookie value:', updatedCookie);
  1290. return updatedCookie;
  1291. } catch (ex) {
  1292. err(ex);
  1293. }
  1294. return '';
  1295. };
  1296. makeCookie('views', '0', { domain: `.${this.host}` });
  1297. // If we are on `/interstitial?viewkey=`
  1298. if (isFN(win.clearModalCookie) && this.webpage.searchParams.has('viewkey')) {
  1299. const videoURL = `${this.webpage.origin}/view_video.php?viewkey=${this.webpage.searchParams.get('viewkey')}`;
  1300. info(`Redirecting to "${videoURL}"`);
  1301. window.location.href = videoURL;
  1302. return;
  1303. }
  1304. }
  1305. }
  1306. if (isNull(loadCSS(mainCSS, 'primary-stylesheet'))) {
  1307. throw new Error('Failed to initialize script!', { cause: 'loadCSS' });
  1308. }
  1309. if (isEmpty(this.frame)) {
  1310. throw new Error('Failed to initialize script!', { cause: 'inject' });
  1311. }
  1312. const overlay = make('mph-elem', 'mph_overlay');
  1313. const header = make('mph-elem', 'mph_list_header');
  1314. const closeVQ = make('mph-elem', 'mgp_title', {
  1315. innerHTML: $info.script.name
  1316. });
  1317. const closeHM = make('mph-close', '', {
  1318. innerHTML: '🗙',
  1319. dataset: {
  1320. command: 'close'
  1321. }
  1322. });
  1323. const listToggle = make('mph-btn', 'of_btn', {
  1324. title: 'Hide/show list',
  1325. textContent: 'Show List ',
  1326. dataset: {
  1327. command: 'toggle-list'
  1328. }
  1329. });
  1330. const listCounter = make('mph-count', '', {
  1331. textContent: '(0)'
  1332. });
  1333. listToggle.append(listCounter);
  1334. header.append(closeVQ, closeHM);
  1335. dContainer.append(header, tab.el.head, dul);
  1336. this.frame.append(dContainer);
  1337. mphControls.append(overlay);
  1338. if (/onlyfans/.test(this.current.root)) {
  1339. const ofsHeader = make('mph-elem', 'mph_of_header');
  1340. ofsHeader.append(ofscopy, ofsdwn);
  1341. header.append(ofsHeader);
  1342. ofscopy.append(copyCounter);
  1343. ofsdwn.append(dwnCounter);
  1344. }
  1345. mphControls.append(listToggle);
  1346. progressFrame.append(progressElem);
  1347. doc.documentElement.append(this.frame, progressFrame, mphControls);
  1348. this.injected = true;
  1349. if (isFN(callback)) {
  1350. callback.call(this, doc);
  1351. }
  1352. } catch (ex) {
  1353. err(ex);
  1354. this.remove();
  1355. }
  1356. }
  1357. themeHandler() {
  1358. return {
  1359. background: null,
  1360. border: null,
  1361. 'controls-background': null,
  1362. color: null,
  1363. header: null,
  1364. hover: null,
  1365. root: null,
  1366. get() {
  1367. return this.color !== null ? this.color : this.find();
  1368. },
  1369. find() {
  1370. const p = this.current ? this.current.root : window.location.host;
  1371. if (/tube8/.test(p)) {
  1372. this.color = 'hsl(201, 64%, 40%)';
  1373. this.hover = 'hsl(201, 64%, 25%)';
  1374. this.background = 'hsl(0, 0%, 0%)';
  1375. this.border = this.color;
  1376. } else if (/thumbzilla/.test(p)) {
  1377. this.color = 'hsl(168, 75%, 42%)';
  1378. this.hover = 'hsl(168, 75%, 27%)';
  1379. this.background = 'hsl(0, 0%, 0%)';
  1380. this.border = this.color;
  1381. } else if (/redtube/.test(p)) {
  1382. this.color = 'hsl(357, 76%, 39%)';
  1383. this.hover = 'hsl(357, 76%, 24%)';
  1384. this.background = 'hsl(0, 0%, 0%)';
  1385. this.border = this.color;
  1386. } else if (/youporn/.test(p)) {
  1387. this.color = 'hsl(345, 80%, 63%)';
  1388. this.hover = 'hsl(345, 80%, 48%)';
  1389. this.background = 'hsl(0, 0%, 0%)';
  1390. this.border = this.color;
  1391. } else if (/onlyfans/.test(p)) {
  1392. this.color = 'var(--text-color, var(--mph-text-color, hsl(210, 12%, 97%)))';
  1393. this.hover = 'var(--swiper-theme-color, hsl(196, 100%, 32%))';
  1394. this.background = 'rgba(138,150,163,.12)';
  1395. this['controls-background'] =
  1396. 'var(--overlay-color, var(--mph-controls-bg-color, hsla(0, 0%, 0%, 0.5)))';
  1397. this.border = this.background;
  1398. this.root = 'var(--overlay-color, var(--mph-controls-bg-color, hsla(0, 0%, 0%, 0.5)))';
  1399. this.header = this.root;
  1400. } else if (/xhamster/.test(p)) {
  1401. this.color = 'var(--color-white-origin, #fff)';
  1402. this.hover = '#d42025';
  1403. this.background = 'var(--color-accent-red, #e34449)';
  1404. this.border = this.background;
  1405. } else {
  1406. this.color = 'hsl(36, 100%, 50%)';
  1407. this.hover = 'hsl(36, 100%, 35%)';
  1408. this.background = 'hsl(0, 0%, 0%)';
  1409. this.border = this.color;
  1410. }
  1411. return this.color;
  1412. },
  1413. load() {
  1414. const root = qs(':root');
  1415. if (!root) {
  1416. err('"root" not found');
  1417. return this;
  1418. }
  1419. if (!this.color) {
  1420. this.get();
  1421. }
  1422. root.style.setProperty('--mph-site-color', this.color);
  1423. if (this.hover) {
  1424. root.style.setProperty('--mph-hover-color', this.hover);
  1425. }
  1426. if (this.background) {
  1427. root.style.setProperty('--mph-background-color', this.background);
  1428. }
  1429. if (this.border) {
  1430. root.style.setProperty('--mph-border-color', this.border);
  1431. }
  1432. if (this.root) {
  1433. root.style.setProperty('--mph-root-bg', this.root);
  1434. }
  1435. if (this.header) {
  1436. root.style.setProperty('--mph-header-bg', this.header);
  1437. }
  1438. return this;
  1439. }
  1440. };
  1441. }
  1442. static domainDefaults = {
  1443. validDomain: false,
  1444. validPath: false,
  1445. pathType: 'Unknown'
  1446. };
  1447. static videoDefaults = {
  1448. title: 'MagicPH',
  1449. mediaFiles: [],
  1450. playerId: 0
  1451. };
  1452. /**
  1453. * @type { import("../typings/types.d.ts").HandlePage['current'] }
  1454. */
  1455. get current() {
  1456. return this.cache;
  1457. }
  1458. set current(url) {
  1459. const urlObj = strToURL(url || window.location);
  1460. const { host } = urlObj;
  1461. this.webpage = urlObj;
  1462. this.host = this.getHost(host);
  1463. /** @type { string } */
  1464. const d = host.split('.').at(-2);
  1465. const root = this.hosts[d] ? d : 'blank';
  1466. const hostDom = {
  1467. ...HandlePage.domainDefaults,
  1468. ...(this.hosts[d] ?? {})
  1469. };
  1470. const routes = new Map();
  1471. if (this.hosts[d]) {
  1472. const findIn = (reg, type = 'domains') => {
  1473. return hostDom[type].find(
  1474. (h) => (isRegExp(reg) && reg.test(h)) || (typeof reg === 'string' && reg.includes(h))
  1475. );
  1476. };
  1477. if (findIn(/pornhub|tube8|youporn|thumbzilla|redtube/)) {
  1478. if (findIn(/pornhub/)) {
  1479. routes.set('GIF', /^\/gif\/\d+(?:\/(?=$))?$/i);
  1480. routes.set('Shorties', /^\/shorties(?:\/(?=$))?$/i);
  1481. }
  1482. if (findIn(/redtube/)) {
  1483. routes.set('Video', /\/[\d]+/g);
  1484. } else {
  1485. routes.set('Video', /(video|watch)+|\/[\d]+\//g);
  1486. }
  1487. }
  1488. for (const [k, v] of routes) {
  1489. if (isRegExp(v) && v.test(urlObj.pathname)) {
  1490. hostDom.validPath = true;
  1491. hostDom.pathType = k;
  1492. break;
  1493. } else if (typeof v === 'string' && v.includes(urlObj.pathname)) {
  1494. hostDom.validPath = true;
  1495. hostDom.pathType = k;
  1496. break;
  1497. }
  1498. }
  1499. // if (/onlyfans/.test(root)) {
  1500. // hostDom.validPath = true;
  1501. // hostDom.pathType = urlObj.pathname;
  1502. // }
  1503. if (!hostDom.validPath) {
  1504. hostDom.pathType = 'Unknown';
  1505. }
  1506. if (findIn(urlObj.host, 'domains')) {
  1507. hostDom.validDomain = true;
  1508. } else {
  1509. hostDom.validDomain = false;
  1510. }
  1511. }
  1512. this.cache = {
  1513. webpage: this.webpage,
  1514. host: this.host,
  1515. root,
  1516. routes,
  1517. ...hostDom
  1518. };
  1519. }
  1520. get Video() {
  1521. return this.videoData;
  1522. }
  1523. set Video(obj = {}) {
  1524. this.videoData = {
  1525. ...this.videoData,
  1526. ...obj
  1527. };
  1528. }
  1529. /**
  1530. * @template { string } S
  1531. * @param { S } str
  1532. * @returns { S }
  1533. */
  1534. getHost(str = '') {
  1535. return str.split('.').splice(-2).join('.');
  1536. }
  1537. refresh() {
  1538. dom.cl.add(qsA('mph-list', dul), 'hidden');
  1539. }
  1540. updateCounters(num, ...hosts) {
  1541. for (const h of hosts) {
  1542. dom.text(qsA(`mph-count[data-host="${h}"]`), ` (${num ?? 0})`);
  1543. }
  1544. dom.text(qsA('mph-count:not([data-host])'), `(${num ?? 0})`);
  1545. }
  1546. remove() {
  1547. if (this.frame) {
  1548. this.frame.remove();
  1549. }
  1550. }
  1551. }
  1552. const HP = new HandlePage();
  1553. // #endregion
  1554. class Timeout {
  1555. constructor() {
  1556. this.ids = [];
  1557. }
  1558. /**
  1559. * Set the Delay and reason for timeout
  1560. * @param { number } localDelay - Delay in ms
  1561. * @param { string } reason - Reason for timeout
  1562. * @returns { Promise<void> } Promise Function
  1563. */
  1564. set = (localDelay, reason) =>
  1565. new Promise((resolve, reject) => {
  1566. const id = setTimeout(() => {
  1567. Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason);
  1568. this.clear(id);
  1569. }, localDelay);
  1570. this.ids.push(id);
  1571. });
  1572. wrap = (promise, localDelay, reason) => Promise.race([promise, this.set(localDelay, reason)]);
  1573. clear = (...ids) => {
  1574. this.ids = this.ids.filter((id) => {
  1575. if (ids.includes(id)) {
  1576. clearTimeout(id);
  1577. return false;
  1578. }
  1579. return true;
  1580. });
  1581. };
  1582. }
  1583. // #endregion
  1584.  
  1585. //#region Icon SVGs
  1586. const iconSVG = {
  1587. close: {
  1588. viewBox: '0 0 384 512',
  1589. html: '<path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/>'
  1590. },
  1591. copy: {
  1592. viewBox: '0 0 448 512',
  1593. html: '<path d="M208 0L332.1 0c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9L448 336c0 26.5-21.5 48-48 48l-192 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48zM48 128l80 0 0 64-64 0 0 256 192 0 0-32 64 0 0 48c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 176c0-26.5 21.5-48 48-48z"/>'
  1594. },
  1595. download: {
  1596. viewBox: '0 0 384 512',
  1597. html: '<path d="M64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-288-128 0c-17.7 0-32-14.3-32-32L224 0 64 0zM256 0l0 128 128 0L256 0zM216 232l0 102.1 31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31L168 232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/>'
  1598. },
  1599. open: {
  1600. viewBox: '0 0 512 512',
  1601. html: '<path d="M352 0c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9L370.7 96 201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L416 141.3l41.4 41.4c9.2 9.2 22.9 11.9 34.9 6.9s19.8-16.6 19.8-29.6l0-128c0-17.7-14.3-32-32-32L352 0zM80 32C35.8 32 0 67.8 0 112L0 432c0 44.2 35.8 80 80 80l320 0c44.2 0 80-35.8 80-80l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 112c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-320c0-8.8 7.2-16 16-16l112 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 32z"/>'
  1602. },
  1603. mobileDownload:
  1604. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="mgp_icon magicph-icon" aria-hidden="true"><g class="download"><path d="M3,14 v5 q0,2 2,2 h14 q2,0 2,-2 v-5 M7,10 l4,4 q1,1 2,0 l4,-4 M12,3 v11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" ></path></g></svg>',
  1605. remove:
  1606. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="magicph-icon" aria-hidden="true"><g><path d="M14,3 C14.5522847,3 15,3.44771525 15,4 C15,4.55228475 14.5522847,5 14,5 L13.846,5 L13.1420511,14.1534404 C13.0618518,15.1954311 12.1930072,16 11.1479,16 L4.85206,16 C3.80698826,16 2.93809469,15.1953857 2.8579545,14.1533833 L2.154,5 L2,5 C1.44771525,5 1,4.55228475 1,4 C1,3.44771525 1.44771525,3 2,3 L5,3 L5,2 C5,0.945642739 5.81588212,0.0818352903 6.85073825,0.00548576453 L7,0 L9,0 C10.0543573,0 10.9181647,0.815882118 10.9945142,1.85073825 L11,2 L11,3 L14,3 Z M11.84,5 L4.159,5 L4.85206449,14.0000111 L11.1479,14.0000111 L11.84,5 Z M9,2 L7,2 L7,3 L9,3 L9,2 Z"/></g></svg>',
  1607. video: {
  1608. viewBox: '0 0 576 512',
  1609. html: '<path d="M0 128C0 92.7 28.7 64 64 64l256 0c35.3 0 64 28.7 64 64l0 256c0 35.3-28.7 64-64 64L64 448c-35.3 0-64-28.7-64-64L0 128zM559.1 99.8c10.4 5.6 16.9 16.4 16.9 28.2l0 256c0 11.8-6.5 22.6-16.9 28.2s-23 5-32.9-1.6l-96-64L416 337.1l0-17.1 0-128 0-17.1 14.2-9.5 96-64c9.8-6.5 22.4-7.2 32.9-1.6z"/>'
  1610. },
  1611. load(type, container) {
  1612. const { createElementNS } = safeSelf();
  1613. const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg');
  1614. for (const [k, v] of Object.entries(iconSVG[type])) {
  1615. if (k === 'html') {
  1616. continue;
  1617. }
  1618. svgElem.setAttributeNS(null, k, v);
  1619. svgElem.classList.add('magicph-icon');
  1620. }
  1621. try {
  1622. if (typeof iconSVG[type].html === 'string') {
  1623. svgElem.innerHTML = iconSVG[type].html;
  1624. dom.attr(svgElem, 'id', `mph_${type ?? 'Unknown'}`);
  1625. }
  1626. // eslint-disable-next-line no-unused-vars
  1627. } catch (ex) {
  1628. /* empty */
  1629. }
  1630. if (container) {
  1631. container.appendChild(svgElem);
  1632. return svgElem;
  1633. }
  1634. return svgElem.outerHTML;
  1635. }
  1636. };
  1637. //#endregion
  1638. const saveAs = (blob, filename) => {
  1639. const url = URL.createObjectURL(blob);
  1640. const a = make('a');
  1641. a.href = url;
  1642. a.download = filename;
  1643. a.click();
  1644. URL.revokeObjectURL(url);
  1645. a.remove();
  1646. };
  1647. const arrayConcat = (inputArray) => {
  1648. const totalLength = inputArray.reduce((prev, cur) => prev + cur.length, 0);
  1649. const result = new Uint8Array(totalLength);
  1650. let offset = 0;
  1651. for (const e of inputArray) {
  1652. result.set(e, offset);
  1653. offset += e.length;
  1654. }
  1655. return result;
  1656. };
  1657. /**
  1658. * @type { import("../typings/UserJS.d.ts").Network }
  1659. */
  1660. const Network = {
  1661. async req(url, method = 'GET', responseType = 'json', data, useFetch = false) {
  1662. if (isEmpty(url)) {
  1663. throw new Error('"url" parameter is empty');
  1664. }
  1665. data = Object.assign({}, data);
  1666. method = this.bscStr(method, false);
  1667. responseType = this.bscStr(responseType);
  1668. const params = {
  1669. method,
  1670. ...data
  1671. };
  1672. if (isGM && !useFetch) {
  1673. if (params.credentials) {
  1674. Object.assign(params, {
  1675. anonymous: false
  1676. });
  1677. if (Object.is(params.credentials, 'omit')) {
  1678. Object.assign(params, {
  1679. anonymous: true
  1680. });
  1681. }
  1682. delete params.credentials;
  1683. }
  1684. } else if (params.onprogress) {
  1685. delete params.onprogress;
  1686. }
  1687. return new Promise((resolve, reject) => {
  1688. if (isGM && !useFetch) {
  1689. Network.xmlRequest({
  1690. url,
  1691. responseType,
  1692. ...params,
  1693. onerror: (r_1) => {
  1694. reject(new Error(`${r_1.status} ${url}`));
  1695. },
  1696. onload: (r_1) => {
  1697. if (r_1.status !== 200) reject(new Error(`${r_1.status} ${url}`));
  1698. if (responseType.match(/basic/)) resolve(r_1);
  1699. resolve(r_1.response);
  1700. }
  1701. });
  1702. } else {
  1703. fetch(url, params)
  1704. .then((response_1) => {
  1705. if (!response_1.ok) reject(response_1);
  1706. const check = (str_2 = 'text') => {
  1707. return isFN(response_1[str_2]) ? response_1[str_2]() : response_1;
  1708. };
  1709. if (responseType.match(/buffer/)) {
  1710. resolve(check('arrayBuffer'));
  1711. } else if (responseType.match(/json/)) {
  1712. resolve(check('json'));
  1713. } else if (responseType.match(/text/)) {
  1714. resolve(check('text'));
  1715. } else if (responseType.match(/blob/)) {
  1716. resolve(check('blob'));
  1717. } else if (responseType.match(/formdata/)) {
  1718. resolve(check('formData'));
  1719. } else if (responseType.match(/clone/)) {
  1720. resolve(check('clone'));
  1721. } else if (responseType.match(/document/)) {
  1722. const respTxt = check('text');
  1723. const domParser = new DOMParser();
  1724. if (respTxt instanceof Promise) {
  1725. respTxt.then((txt) => {
  1726. const doc = domParser.parseFromString(txt, 'text/html');
  1727. resolve(doc);
  1728. });
  1729. } else {
  1730. const doc = domParser.parseFromString(respTxt, 'text/html');
  1731. resolve(doc);
  1732. }
  1733. } else {
  1734. resolve(response_1);
  1735. }
  1736. })
  1737. .catch(reject);
  1738. }
  1739. });
  1740. },
  1741. format(bytes, decimals = 2) {
  1742. if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`;
  1743. const k = 1024;
  1744. const dm = decimals < 0 ? 0 : decimals;
  1745. const i = Math.floor(Math.log(bytes) / Math.log(k));
  1746. return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`;
  1747. },
  1748. sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
  1749. async xmlRequest(details) {
  1750. if (isGM) {
  1751. if (isFN(GM.xmlHttpRequest)) {
  1752. return GM.xmlHttpRequest(details);
  1753. } else if (isFN(GM_xmlhttpRequest)) {
  1754. return GM_xmlhttpRequest(details);
  1755. }
  1756. }
  1757. return await new Promise((resolve, reject) => {
  1758. const { XMLHttpRequest } = safeSelf();
  1759. const req = new XMLHttpRequest();
  1760. let method = 'GET';
  1761. let url = BLANK_PAGE;
  1762. let body;
  1763. for (const [key, value] of Object.entries(details)) {
  1764. if (key === 'onload') {
  1765. req.addEventListener('load', () => {
  1766. if (isFN(value)) {
  1767. value(req);
  1768. }
  1769. resolve(req);
  1770. });
  1771. } else if (key === 'onerror') {
  1772. req.addEventListener('error', (evt) => {
  1773. if (isFN(value)) {
  1774. value(evt);
  1775. }
  1776. reject(evt);
  1777. });
  1778. } else if (key === 'onabort') {
  1779. req.addEventListener('abort', (evt) => {
  1780. if (isFN(value)) {
  1781. value(evt);
  1782. }
  1783. reject(evt);
  1784. });
  1785. } else if (key === 'onprogress') {
  1786. req.addEventListener('progress', value);
  1787. } else if (key === 'responseType') {
  1788. if (value === 'buffer') {
  1789. req.responseType = 'arraybuffer';
  1790. } else {
  1791. req.responseType = value;
  1792. }
  1793. } else if (key === 'method') {
  1794. method = value;
  1795. } else if (key === 'url') {
  1796. url = value;
  1797. } else if (key === 'body') {
  1798. body = value;
  1799. }
  1800. }
  1801. req.open(method, url);
  1802.  
  1803. if (isEmpty(req.responseType)) {
  1804. req.responseType = 'text';
  1805. }
  1806.  
  1807. if (body) {
  1808. req.send(body);
  1809. } else {
  1810. req.send();
  1811. }
  1812. });
  1813. },
  1814. bscStr(str = '', lowerCase = true) {
  1815. const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase']();
  1816. return txt.replaceAll(/\W/g, '');
  1817. },
  1818. prog(evt) {
  1819. return Object.is(evt.total, 0)
  1820. ? Network.format(evt.loaded)
  1821. : `${+((evt.loaded / evt.total) * 100).toFixed(2)}%`;
  1822. },
  1823. async stream(url = '', filename, data = {}) {
  1824. try {
  1825. const chunks = [];
  1826. const response = await Network.req(url, 'GET', 'basic', data, true).catch(err);
  1827. if (!response) {
  1828. return new Uint8Array();
  1829. }
  1830. const contentLength = +response.headers.get('Content-Length');
  1831. let receivedLength = 0;
  1832. for await (const chunk of Network.streamAsyncIterable(response.body)) {
  1833. receivedLength += chunk.length;
  1834. chunks.push(chunk);
  1835. if (filename) {
  1836. const percentComplete = Network.prog({
  1837. loaded: receivedLength,
  1838. total: contentLength
  1839. });
  1840. msg(`[MagicPH] (${percentComplete}) Downloading "${filename}" using "Fetch API"`);
  1841. }
  1842. }
  1843. const Uint8Chunks = new Uint8Array(receivedLength);
  1844. let position = 0;
  1845. for (const chunk of chunks) {
  1846. Uint8Chunks.set(chunk, position);
  1847. position += chunk.length;
  1848. }
  1849. return Uint8Chunks;
  1850. } catch (ex) {
  1851. err(ex);
  1852. }
  1853. return null;
  1854. },
  1855. async download(details = {}) {
  1856. return await new Promise((resolve) => {
  1857. Network.stream(details.url, details.name).then((Uint8Chunks) => {
  1858. const blob = new Blob([Uint8Chunks], {
  1859. type: 'application/octet-stream'
  1860. });
  1861. saveAs(blob, details.name);
  1862. resolve(details.name);
  1863. });
  1864. });
  1865. },
  1866. /**
  1867. * @param { ReadableStream<Uint8Array> } stream
  1868. */
  1869. async *streamAsyncIterable(stream) {
  1870. const reader = stream.getReader();
  1871. try {
  1872. while (true) {
  1873. const { done, value } = await reader.read();
  1874. if (done) return;
  1875. yield value;
  1876. }
  1877. } finally {
  1878. reader.releaseLock();
  1879. }
  1880. }
  1881. };
  1882. /**
  1883. * @type { import("../typings/UserJS.d.ts").openTab }
  1884. */
  1885. const openTab = (url) => {
  1886. if (isGM) {
  1887. if (isFN(GM.openInTab)) {
  1888. return GM.openInTab(url);
  1889. } else if (isFN(GM_openInTab)) {
  1890. return GM_openInTab(url, {
  1891. active: true,
  1892. insert: true
  1893. });
  1894. }
  1895. }
  1896. return window.open(url, '_blank');
  1897. };
  1898.  
  1899. const ofUsers = {};
  1900. const videoCache = new Map();
  1901. const hostCache = new Map();
  1902.  
  1903. const dul = make('mph-list');
  1904.  
  1905. class Tabs {
  1906. constructor() {
  1907. this.Tab = new Map();
  1908. this.blank = 'about:blank';
  1909. this.protocal = 'mph:';
  1910. this.protoReg = new RegExp(`${this.protocal}(.+)`);
  1911. this.el = {
  1912. add: make('mph-addtab', '', {
  1913. textContent: '+',
  1914. dataset: {
  1915. command: 'new-tab'
  1916. }
  1917. }),
  1918. head: make('mph-tabs')
  1919. };
  1920. this.el.head.append(this.el.add);
  1921. }
  1922. hasTab(...params) {
  1923. for (const p of params) {
  1924. if (!this.Tab.has(p)) {
  1925. return false;
  1926. }
  1927. const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host);
  1928. if (isBlank(content)) {
  1929. return false;
  1930. }
  1931. }
  1932. return true;
  1933. }
  1934. storeTab(host) {
  1935. const h = host ?? this.blank;
  1936. if (!this.Tab.has(h)) {
  1937. this.Tab.set(h, new Set());
  1938. }
  1939. return this.Tab.get(h);
  1940. }
  1941. cache(host, ...tabs) {
  1942. const h = host ?? this.blank;
  1943. const tabCache = this.storeTab(h);
  1944. for (const t of normalizeTarget(tabs)) {
  1945. if (tabCache.has(t)) {
  1946. continue;
  1947. }
  1948. tabCache.add(t);
  1949. }
  1950. this.Tab.set(h, tabCache);
  1951. return tabCache;
  1952. }
  1953. intFN(host) {
  1954. if (!host.startsWith(this.protocal)) {
  1955. return;
  1956. }
  1957. // const type = host.match(this.protoReg)[1];
  1958. // if (type === 'settings') {
  1959. // dom.cl.remove([cfgpage, exBtn], 'hidden');
  1960. // dom.cl.add(table, 'hidden');
  1961. // if (!container.supported) {
  1962. // dom.attr(container.frame, 'style', 'height: 100%;');
  1963. // }
  1964. // }
  1965. }
  1966. active(tab, build = true) {
  1967. for (const t of normalizeTarget(tab, false)) {
  1968. dom.cl.remove(qsA('mph-tab', this.el.head), 'active');
  1969. const tabContent = qsA('mph-list', dul);
  1970. dom.cl.add(tabContent, 'hidden');
  1971. dom.cl.add(t, 'active');
  1972. if (!build) {
  1973. continue;
  1974. }
  1975. const host = t.dataset.host ?? this.blank;
  1976. if (host !== this.blank) {
  1977. const title = tab.dataset.title;
  1978. const content = normalizeTarget(tabContent).filter(
  1979. (t) => host === t.dataset.host || title === t.dataset.host
  1980. );
  1981. dom.cl.remove(content, 'hidden');
  1982. }
  1983. if (host === this.blank) {
  1984. HP.refresh();
  1985. } else if (host.startsWith(this.protocal)) {
  1986. this.intFN(host);
  1987. }
  1988. }
  1989. }
  1990. /** @param { HTMLElement } tab */
  1991. close(tab) {
  1992. for (const t of normalizeTarget(tab, false)) {
  1993. const host = t.dataset.host;
  1994. if (hostCache.has(host)) {
  1995. hostCache.delete(host);
  1996. }
  1997. if (dom.cl.has(t, 'active')) {
  1998. HP.refresh();
  1999. }
  2000. const sibling = t.previousElementSibling ?? t.nextElementSibling;
  2001. if (sibling) {
  2002. if (sibling.dataset.command !== 'new-tab') {
  2003. this.active(sibling);
  2004. }
  2005. }
  2006. if (this.Tab.has(host)) {
  2007. this.Tab.delete(host);
  2008. }
  2009. t.remove();
  2010. }
  2011. }
  2012. // active(tab) {
  2013. // dom.cl.remove(qsA('mph-tab', ntHead), 'active');
  2014. // const tabContent = qsA('mph-list', dul);
  2015. // dom.cl.add(tabContent, 'hidden');
  2016. // dom.cl.add(tab, 'active');
  2017.  
  2018. // const host = tab.dataset.host ?? this.blank;
  2019. // if (host !== this.blank) {
  2020. // const title = tab.dataset.title;
  2021. // const content = normalizeTarget(tabContent).filter(
  2022. // (t) => host === t.dataset.host || title === t.dataset.host
  2023. // );
  2024. // dom.cl.remove(content, 'hidden');
  2025. // }
  2026. // if (host === this.blank) {
  2027. // HP.refresh();
  2028. // } else if (host.startsWith(this.protocal)) {
  2029. // this.intFN(host);
  2030. // }
  2031. // }
  2032. // /** @param { Element } tab */
  2033. // close(tab) {
  2034. // const host = tab.dataset.host;
  2035. // if (hostCache.has(host)) {
  2036. // hostCache.delete(host);
  2037. // }
  2038. // const sibling = tab.previousElementSibling ?? tab.nextElementSibling;
  2039. // if (sibling) {
  2040. // if (sibling.dataset.command !== 'new-tab') {
  2041. // this.active(sibling);
  2042. // }
  2043. // }
  2044. // if (this.Tab.has(host)) {
  2045. // const tabSet = this.Tab.get(host);
  2046. // if (tabSet.has(tab)) {
  2047. // tabSet.delete(tab);
  2048. // }
  2049. // }
  2050. // tab.remove();
  2051. // }
  2052. create(host = undefined, text) {
  2053. if (typeof host === 'string') {
  2054. if (host.startsWith(this.protocal) && this.hasTab(host)) {
  2055. this.active(this.Tab.get(host));
  2056. return;
  2057. }
  2058. const content = normalizeTarget(this.storeTab(host)).filter((t) => host === t.dataset.host);
  2059. if (!isEmpty(content)) {
  2060. return;
  2061. }
  2062. }
  2063. const tab = make('mph-tab', '', {
  2064. dataset: {
  2065. command: 'switch-tab',
  2066. title: text
  2067. },
  2068. style: `order: ${this.el.head.childElementCount};`
  2069. });
  2070. const tabClose = make('mph-elem', '', {
  2071. dataset: {
  2072. command: 'close-tab'
  2073. },
  2074. title: i18n$('close'),
  2075. textContent: 'X'
  2076. });
  2077. // const tabHost = make('mujs-host');
  2078. // tab.append(tabHost, tabClose);
  2079. // this.el.head.append(tab);
  2080. // this.active(tab, false);
  2081. // this.cache(host, tab);
  2082. // if (isNull(host)) {
  2083. // HP.refresh();
  2084. // urlBar.placeholder = i18n$('newTab');
  2085. // tab.dataset.host = this.blank;
  2086. // tabHost.title = i18n$('newTab');
  2087. // tabHost.textContent = i18n$('newTab');
  2088. // } else if (host.startsWith(this.protocal)) {
  2089. // const type = host.match(this.protoReg)[1];
  2090. // tab.dataset.host = host || HP.host;
  2091. // tabHost.title = type || tab.dataset.host;
  2092. // tabHost.textContent = tabHost.title;
  2093. // this.intFN(host);
  2094. // } else {
  2095. // tab.dataset.host = host || HP.host;
  2096. // tabHost.title = host || HP.host;
  2097. // tabHost.textContent = tabHost.title;
  2098. // }
  2099. // return tab;
  2100.  
  2101. const tabBox = make('mph-host');
  2102. tab.append(tabBox, tabClose);
  2103.  
  2104. this.el.head.append(tab);
  2105. this.active(tab, false);
  2106. this.cache(host, tab);
  2107.  
  2108. // dom.cl.remove(qsA('mph-tab', ntHead), 'active');
  2109. // dom.cl.add(qsA('mph-list', dul), 'hidden');
  2110. // dom.cl.add(tab, 'active');
  2111.  
  2112. if (isNull(host)) {
  2113. tab.dataset.host = 'about:blank';
  2114. tabBox.title = i18n$('newTab');
  2115. const siteLoader = async (val) => {
  2116. const value = {
  2117. type: '',
  2118. txt: val,
  2119. url: ''
  2120. };
  2121. let qualities = [];
  2122. const loadMedia = async (str = '') => {
  2123. const s = str.replaceAll('\\', '');
  2124. const media = new mphMedia(s);
  2125. qualities = await media.autoStart(s);
  2126. if (!isEmpty(qualities)) {
  2127. tab.dataset.host = media.title;
  2128. tabBox.title = media.title;
  2129. tabBox.textContent = media.title;
  2130. const count = make('mph-count', '', {
  2131. textContent: ' (0)',
  2132. dataset: {
  2133. host: media.title ?? 'about:blank'
  2134. }
  2135. });
  2136. tabBox.append(count);
  2137. makeContainer(qualities, {
  2138. ts: tsSrc,
  2139. title: media.title
  2140. });
  2141. }
  2142. };
  2143. // if (val.startsWith('mph:')) {
  2144. // this.close(tab);
  2145. // if (this.Tab.has(val)) {
  2146. // this.active(this.Tab.get(val));
  2147. // } else {
  2148. // this.make(val);
  2149. // }
  2150. // return null;
  2151. // } else if (val === '*') {
  2152. // tab.dataset.host = val;
  2153. // tabBox.title = '<All Sites>';
  2154. // tabBox.textContent = '<All Sites>';
  2155. // }
  2156. if (val.startsWith('http')) {
  2157. const url = strToURL(val);
  2158. if (objToStr(url).includes('URL')) {
  2159. value.url = url;
  2160. if (/onlyfans/.test(HP.current.root) && !/onlyfans/.test(url.host)) {
  2161. throw new Error('Please navigate to "onlyfans.com"');
  2162. }
  2163. await loadMedia(value.url.href);
  2164. }
  2165. } else if (/^\/video-\w+\/[\w-.]+(?:\/(?=$))?$/i.test(val)) {
  2166. if (/xnxx/.test(HP.current.root)) {
  2167. value.url = `${HP.webpage.origin}${val}`;
  2168. } else {
  2169. value.url = `https://www.xnxx.com${val}`;
  2170. }
  2171. await loadMedia(value.url);
  2172. } else if (/^\/videos\/[a-z0-9._-]+(?:\/(?=$))?$/i.test(val)) {
  2173. if (/xhamster/.test(HP.current.root)) {
  2174. value.url = `${HP.webpage.origin}${val}`;
  2175. } else {
  2176. value.url = `https://xhamster.com${val}`;
  2177. }
  2178. await loadMedia(value.url);
  2179. } else if (/^\/watch\/\d+\/[\w-]+(?:\/(?=$))?$/i.test(val)) {
  2180. if (/youporn/.test(HP.current.root)) {
  2181. value.url = `${HP.webpage.origin}${val}`;
  2182. } else {
  2183. value.url = `https://www.youporn.com${val}`;
  2184. }
  2185. await loadMedia(value.url);
  2186. } else if (/^\/video\/\w+\/[\w-]+(?:\/(?=$))?$/i.test(val)) {
  2187. if (/thumbzilla/.test(HP.current.root)) {
  2188. value.url = `${HP.webpage.origin}${val}`;
  2189. } else {
  2190. value.url = `https://www.thumbzilla.com${val}`;
  2191. }
  2192. await loadMedia(value.url);
  2193. } else if (/^\/porn-video\/\d{8}(?:\/(?=$))?$/i.test(val)) {
  2194. if (/tube8/.test(HP.current.root)) {
  2195. value.url = `${HP.webpage.origin}${val}`;
  2196. } else {
  2197. value.url = `https://www.tube8.com${val}`;
  2198. }
  2199. await loadMedia(value.url);
  2200. } else if (/^\/(\d{8})?$/i.test(val)) {
  2201. if (/redtube/.test(HP.current.root)) {
  2202. value.url = `${HP.webpage.origin}${val}`;
  2203. } else {
  2204. value.url = `https://www.redtube.com${val}`;
  2205. }
  2206. await loadMedia(value.url);
  2207. } else if (/^\/(view_video\.php\?viewkey=\w+)?$/i.test(val)) {
  2208. if (/pornhub/.test(HP.current.root)) {
  2209. value.url = `${HP.webpage.origin}${val}`;
  2210. } else {
  2211. value.url = `https://www.pornhub.com${val}`;
  2212. }
  2213. await loadMedia(value.url);
  2214. } else if (
  2215. /onlyfans/.test(HP.current.root) &&
  2216. /^\/((?:[a-z0-9][a-z0-9._-]+))(?:\/(?=$))?$/i.test(val)
  2217. ) {
  2218. value.url = `https://onlyfans.com${val}/videos`;
  2219. await loadMedia(value.url);
  2220. } else if (
  2221. /onlyfans/.test(HP.current.root) &&
  2222. /^\/((?:[a-z0-9][a-z0-9._-]+))\/((?:media|photos|videos|audios|likes|streams|upcoming-streams))(?:\/(?=$))?$/i.test(
  2223. val
  2224. )
  2225. ) {
  2226. value.url = `https://onlyfans.com${val}`;
  2227. await loadMedia(value.url);
  2228. } else if (/\w{13,15}/.test(val)) {
  2229. value.txt = val.match(/\w{13,15}/)[0];
  2230. if (/pornhub/.test(HP.current.root)) {
  2231. value.url = `${HP.webpage.origin}/view_video.php?viewkey=${value.txt}`;
  2232. } else {
  2233. value.url = `https://www.pornhub.com/view_video.php?viewkey=${value.txt}`;
  2234. }
  2235. await loadMedia(value.url);
  2236. }
  2237. // else {
  2238. // value.txt = HP.getHost(val);
  2239. // newHost = value.txt;
  2240. // tab.dataset.host = value.txt;
  2241. // tabBox.title = value.txt;
  2242. // tabBox.textContent = value.txt;
  2243. // }
  2244. if (!isEmpty(qualities)) {
  2245. this.cache(value.txt, tab);
  2246. }
  2247. return qualities;
  2248. };
  2249. const siteSearcher = make('input', 'mph-searcher', {
  2250. autocomplete: 'off',
  2251. spellcheck: false,
  2252. type: 'text',
  2253. placeholder: i18n$('newTab'),
  2254. onchange(evt) {
  2255. evt.preventDefault();
  2256. siteLoader(evt.target.value).then((h) => {
  2257. if (isEmpty(h)) {
  2258. tabBox.title = 'An error occured';
  2259. tabBox.textContent = 'An error occured';
  2260. return;
  2261. }
  2262. siteSearcher.remove();
  2263. });
  2264. }
  2265. });
  2266. tabBox.append(siteSearcher);
  2267. return tab;
  2268. }
  2269. let tabHost;
  2270. let tabTitle;
  2271. let tabText;
  2272. if (typeof host === 'string' && host.startsWith('mph:')) {
  2273. const type = host.match(/mph:(.+)/)[1];
  2274. tabHost = host || HP.host;
  2275. tabTitle = text || type || tabHost;
  2276. tabText = text || tabTitle;
  2277. this.intFN(host);
  2278. } else {
  2279. tabHost = host || HP.host;
  2280. tabTitle = text || host || dom.attr(qs('meta[property="og:title"]'), 'content') || HP.host;
  2281. tabText = text || tabTitle;
  2282. }
  2283. tab.dataset.host = tabHost;
  2284. tabBox.title = tabTitle;
  2285. tabBox.textContent = tabText;
  2286. const count = make('mph-count', '', {
  2287. textContent: ' (0)',
  2288. dataset: {
  2289. host: tabHost ?? 'about:blank'
  2290. }
  2291. });
  2292. tabBox.append(count);
  2293. return tab;
  2294. }
  2295. }
  2296. const tab = new Tabs();
  2297. const progressElem = make('h1', 'mph_progress');
  2298. const progressFrame = make('mph-elem', 'mph_progressContainer');
  2299. // const frame = make('main-userjs', 'hidden', {
  2300. // dataset: {
  2301. // insertedBy: 'magic-ph',
  2302. // role: 'primary-container'
  2303. // }
  2304. // });
  2305. const dContainer = make('mph-root', '', {
  2306. async onclick(evt) {
  2307. try {
  2308. /** @type { HTMLElement } */
  2309. const target = evt.target.closest('[data-command]');
  2310. if (!target) {
  2311. return;
  2312. }
  2313. const dataset = target.dataset;
  2314. const cmd = dataset.command;
  2315. if (cmd.startsWith('of-')) {
  2316. if (cmd === 'of-copy') {
  2317. const d = target.parentElement.parentElement.dataset;
  2318. const vid = getOFQuality(d);
  2319. if (!vid) {
  2320. return;
  2321. }
  2322. await writeClipboard(vid.src);
  2323. msg('[MagicPH] Copied URL to Clipboard', 2500);
  2324. } else if (cmd === 'of-download') {
  2325. const d = target.parentElement.parentElement.dataset;
  2326. const vid = getOFQuality(d);
  2327. if (!vid) {
  2328. return;
  2329. }
  2330. const Uint8Chunks = await Network.stream(vid.src, `${cleanURL(vid.title)}.mp4`);
  2331. const blob = new Blob([Uint8Chunks], {
  2332. type: 'application/octet-stream'
  2333. });
  2334. saveAs(blob, `${cleanURL(vid.title)}.mp4`);
  2335. msg('[MagicPH] Downloads complete!', 2500);
  2336. } else if (cmd === 'of-remove') {
  2337. const d = target.parentElement.parentElement.dataset;
  2338. if (!videoCache.has(d.host)) {
  2339. return;
  2340. }
  2341. msg(`[MagicPH] Deleted Video Id: ${d.host}`, 2500);
  2342. videoCache.delete(d.host);
  2343. target.parentElement.parentElement.remove();
  2344. } else if (cmd === 'remove-all') {
  2345. videoCache.clear();
  2346. dom.remove(qsA('.mph_of_list > .wrap'));
  2347. } else if (cmd === 'of-copy-all') {
  2348. const arr = [];
  2349. for (const v of videoCache.values()) {
  2350. for (const vid of v) {
  2351. if (vid.quality === 'original') {
  2352. arr.push(vid.src);
  2353. }
  2354. }
  2355. }
  2356. await writeClipboard(arr.join('\n'));
  2357. msg('[MagicPH] Copied URLs to Clipboard', 2500);
  2358. } else if (cmd === 'of-download-all') {
  2359. for (const vid of videoCache.values()) {
  2360. const params = {
  2361. name: `${cleanURL(vid.title)}.mp4`,
  2362. url: vid.src
  2363. };
  2364. if (Limit_Downloads || videoCache.size > 16 || isMobile) {
  2365. await Network.download(params);
  2366. } else {
  2367. Network.download(params);
  2368. }
  2369. dom.remove(qs(`.mph_of_list > .wrap[data-title="${vid.title}"]`));
  2370. videoCache.delete(vid.title);
  2371. }
  2372. } else if (cmd === 'of-open-tab' && dataset.webpage) {
  2373. vueRouter.push(dataset.webpage);
  2374. return;
  2375. }
  2376. ofsdwn.innerHTML = `Download (${videoCache.size})`;
  2377. ofscopy.innerHTML = `${i18n$('copy')} (${videoCache.size})`;
  2378. ofsRm.innerHTML = `Remove (${videoCache.size})`;
  2379. return;
  2380. }
  2381. if (cmd === 'open-tab' && dataset.webpage) {
  2382. openTab(dataset.webpage);
  2383. } else if (cmd === 'new-tab') {
  2384. tab.create();
  2385. } else if (cmd === 'switch-tab') {
  2386. tab.active(target);
  2387. } else if (cmd === 'close-tab' && target.parentElement) {
  2388. tab.close(target.parentElement);
  2389. } else if (cmd === 'close') {
  2390. dom.cl.remove(mphControls, 'hidden');
  2391. dom.remove(qsA('video', HP.frame));
  2392. dom.cl.remove(HP.frame, 'expanded');
  2393. dom.cl.add(HP.frame, 'hidden');
  2394. } else if (cmd === 'copy' && dataset.webpage) {
  2395. await writeClipboard(dataset.webpage);
  2396. const inp = qs('input', target.parentElement);
  2397. msg('[MagicPH] Copied URL to Clipboard', 2500);
  2398. if (HP.host.includes('xhamster')) {
  2399. inp.style.color = HP.theme.background;
  2400. } else {
  2401. inp.style.color = HP.theme.get();
  2402. }
  2403. await delay(2500);
  2404. dom.attr(inp, 'style', '');
  2405. } else if (cmd === 'download-video' && dataset.webpage && videoCache.has(dataset.webpage)) {
  2406. const vid = videoCache.get(dataset.webpage);
  2407. const t = cleanURL(vid.title);
  2408. if (vid.isStr) {
  2409. await Network.download(
  2410. {
  2411. name: `${t}.mp4`,
  2412. url: dataset.webpage,
  2413. hermes: vid.data
  2414. },
  2415. true
  2416. );
  2417. msg('[MagicPH] Download complete', 2500);
  2418. } else {
  2419. const a = make('a');
  2420. a.href = dataset.webpage;
  2421. a.download = `${t}.ts`;
  2422. a.click();
  2423. a.remove();
  2424. }
  2425. } else if (cmd === 'preview-video' && dataset.webpage && videoCache.has(dataset.webpage)) {
  2426. let pageArea = qs('mph-page', target.parentElement);
  2427. if (!pageArea) {
  2428. pageArea = make('mph-page');
  2429. target.parentElement.append(pageArea);
  2430. if (HP.supported) {
  2431. const shadow = pageArea.attachShadow({ mode: 'closed' });
  2432. const videoElem = make('video', '', {
  2433. preload: 'auto'
  2434. // style: 'max-width: 720px; max-height: 720px;'
  2435. });
  2436. dom.attr(videoElem, 'controls', '');
  2437. dom.attr(videoElem, 'disablepictureinpicture', '');
  2438. const src = make('source', '', {
  2439. src: dataset.webpage,
  2440. type: 'video/mp4'
  2441. });
  2442. videoElem.append(src);
  2443. shadow.append(videoElem);
  2444. dom.cl.add(HP.frame, 'expanded');
  2445. }
  2446. return;
  2447. }
  2448. if (!dom.cl.has(pageArea, 'hidden')) {
  2449. dom.cl.add(pageArea, 'hidden');
  2450. dom.cl.remove(HP.frame, 'expanded');
  2451. return;
  2452. }
  2453. dom.cl.remove(pageArea, 'hidden');
  2454. dom.cl.add(HP.frame, 'expanded');
  2455. } else if (cmd === 'load-ts' && dataset.webpage && videoCache.has(dataset.webpage)) {
  2456. const vid = videoCache.get(dataset.webpage);
  2457. const hc = hostCache.get(vid.title);
  2458. const h = new mphHLS(dataset.webpage, hc.hermes, target);
  2459. const q = await h.start();
  2460. if (!isEmpty(q)) {
  2461. h.msg();
  2462. if (hc.rows.has('loadTS')) {
  2463. hc.rows.delete('loadTS');
  2464. }
  2465. makeContainer(q, {
  2466. rows: ['title', 'download'],
  2467. $el: target
  2468. });
  2469. }
  2470. }
  2471. } catch (ex) {
  2472. err(ex);
  2473. }
  2474. }
  2475. });
  2476. const msg = async (text, time) => {
  2477. const notice = progressFrame ?? qs('.mph_progressContainer');
  2478. if (!notice) {
  2479. return;
  2480. }
  2481. const noticeElem = progressElem ?? qs('.mph_progress');
  2482. if (!noticeElem) {
  2483. return;
  2484. }
  2485. const timeout = new Timeout();
  2486.  
  2487. if (!isNull(text) && !isNull(time)) {
  2488. noticeElem.innerHTML = text;
  2489. notice.setAttribute('style', 'display: block;');
  2490. await timeout.set(time);
  2491. notice.setAttribute('style', '');
  2492. return timeout.clear(...timeout.ids);
  2493. }
  2494.  
  2495. if (typeof text === 'number' && !Number.isNaN(text)) {
  2496. await timeout.set(text);
  2497. notice.setAttribute('style', '');
  2498. } else if (!isNull(text)) {
  2499. noticeElem.innerHTML = text;
  2500. notice.setAttribute('style', 'display: block;');
  2501. }
  2502. if (!isNull(time)) {
  2503. if (isBlank(notice.style.display)) {
  2504. notice.setAttribute('style', 'display: block;');
  2505. }
  2506. await timeout.set(time);
  2507. notice.setAttribute('style', '');
  2508. }
  2509. return timeout.clear(...timeout.ids);
  2510. };
  2511. const getOFQuality = (d, quality = 'original') => {
  2512. if (!videoCache.has(d.host)) {
  2513. return null;
  2514. }
  2515. for (const vid of videoCache.get(d.host)) {
  2516. if (vid.title !== d.title) {
  2517. continue;
  2518. }
  2519. if (vid.quality === quality) {
  2520. return vid;
  2521. }
  2522. }
  2523. return videoCache.get(d.host)[0];
  2524. };
  2525. const mphControls = make('mph-controls', '', {
  2526. async click(evt) {
  2527. const target = evt.target;
  2528. if (!target.dataset) {
  2529. return;
  2530. }
  2531. if (!target.dataset.command) {
  2532. return;
  2533. }
  2534. const cmd = target.dataset.command;
  2535. if (cmd === 'of-copy') {
  2536. const vid = getOFQuality();
  2537. if (!vid) {
  2538. return;
  2539. }
  2540. await writeClipboard(vid.src);
  2541. msg('[MagicPH] Copied URL to Clipboard', 2500);
  2542. } else if (cmd === 'of-download') {
  2543. const vid = getOFQuality();
  2544. if (!vid) {
  2545. return;
  2546. }
  2547. await Network.download({
  2548. name: `${cleanURL(vid.title)}.mp4`,
  2549. url: vid.src
  2550. });
  2551. msg('[MagicPH] Downloads complete!', 2500);
  2552. } else if (cmd === 'toggle-list') {
  2553. if (dom.cl.has(mphControls, 'hidden')) {
  2554. dom.cl.remove(mphControls, 'hidden');
  2555. dom.cl.add(HP.frame, 'hidden');
  2556. return;
  2557. }
  2558. dom.cl.remove(HP.frame, 'hidden');
  2559. dom.cl.add(mphControls, 'hidden');
  2560. }
  2561. }
  2562. });
  2563. const dwnCounter = make('mph-count', '', {
  2564. innerHTML: '(0)'
  2565. });
  2566. const copyCounter = make('mph-count', '', {
  2567. innerHTML: '(0)'
  2568. });
  2569. const addToWrapper = () => {
  2570. if (videoCache.size === 0) {
  2571. return;
  2572. }
  2573. for (const [userId, userVideos] of videoCache) {
  2574. const mphList =
  2575. qs(`mph-list > mph-list[data-host="${userId}"]`) ??
  2576. make('mph-list', 'mph_of_list', {
  2577. dataset: {
  2578. host: userId
  2579. }
  2580. });
  2581. const hc = hostCache.get(userId);
  2582. tab.create(userId);
  2583. for (const v of userVideos) {
  2584. if (qsA(`[data-title="${v.title}"]`, mphList).length !== 0) {
  2585. continue;
  2586. }
  2587.  
  2588. const $el = make('mph-elem', 'wrap', {
  2589. dataset: {
  2590. title: v.title,
  2591. host: userId
  2592. }
  2593. });
  2594. const imgC = make('mph-a', 'self-container', {
  2595. dataset: {
  2596. command: 'open-tab',
  2597. webpage: v.src
  2598. }
  2599. });
  2600. const btns = make('mph-elem', 'btn-container');
  2601. const cpBtn = make('mph-btn', 'of_btn', {
  2602. title: i18n$('copy'),
  2603. innerHTML: i18n$('copy'),
  2604. dataset: {
  2605. command: 'of-copy'
  2606. }
  2607. });
  2608. const downBtn = make('mph-btn', 'of_btn', {
  2609. title: i18n$('download'),
  2610. innerHTML: i18n$('download'),
  2611. dataset: {
  2612. command: 'of-download'
  2613. }
  2614. });
  2615. const rmBtn = make('mph-btn', 'of_btn', {
  2616. title: i18n$('remove'),
  2617. innerHTML: i18n$('remove'),
  2618. dataset: {
  2619. command: 'of-remove'
  2620. }
  2621. });
  2622. const img = new Image();
  2623. img.alt = '';
  2624. img.referrerPolicy = 'no-referrer';
  2625. img.src = v.poster;
  2626. img.onload = () => {
  2627. imgC.append(img);
  2628. };
  2629. const sp = make('mph-title');
  2630. const fTitle = make('mph-a', '', {
  2631. dataset: {
  2632. command: 'of-open-tab',
  2633. webpage: v.original
  2634. },
  2635. title: v.text,
  2636. innerHTML: v.title
  2637. });
  2638. btns.prepend(cpBtn, downBtn, rmBtn);
  2639. sp.append(fTitle);
  2640. $el.append(btns, imgC, sp);
  2641. if (v.duration || v.frame) {
  2642. const moreInfo = make('mph-elem', 'more-info');
  2643. if (v.duration) {
  2644. const duration = make('mph-elem', '', {
  2645. textContent: v.duration
  2646. });
  2647. moreInfo.append(duration);
  2648. }
  2649. if (v.frame) {
  2650. const frame = make('mph-elem', '', {
  2651. textContent: v.frame
  2652. });
  2653. moreInfo.append(frame);
  2654. }
  2655. $el.append(moreInfo);
  2656. }
  2657. if (!mphList.contains($el)) {
  2658. mphList.append($el);
  2659. hc.$elems.push($el);
  2660. }
  2661. }
  2662. hostCache.set(userId, hc);
  2663. dul.append(mphList);
  2664. HP.updateCounters(userVideos.length, userId);
  2665. }
  2666. };
  2667. const ofsdwn = make('mph-btn', 'of_btn', {
  2668. title: 'Download available videos',
  2669. textContent: 'Download ',
  2670. dataset: {
  2671. command: 'of-download-all'
  2672. }
  2673. });
  2674. const ofscopy = make('mph-btn', 'of_btn', {
  2675. title: 'Copy all available videos to clipboard',
  2676. textContent: `${i18n$('copy')} `,
  2677. dataset: {
  2678. command: 'of-copy-all'
  2679. }
  2680. });
  2681. const ofsRm = make('mph-btn', 'of_btn', {
  2682. title: 'Remove all available videos',
  2683. innerHTML: 'Remove All',
  2684. dataset: {
  2685. command: 'of-remove-all'
  2686. }
  2687. });
  2688. const vidQuality = make('div', 'mgp_download mgp_optionSelector', {
  2689. innerHTML: $info.script.name,
  2690. onclick(evt) {
  2691. evt.preventDefault();
  2692. evt.stopPropagation();
  2693. dom.cl.remove(HP.frame, 'hidden');
  2694. if (isMobile) {
  2695. if (qs('div.mgp_controls > div.mgp_qualitiesMenu') || HP.host.includes('youporn')) {
  2696. dom.cl.add('.mgp_contextMenu', 'mgp_hidden');
  2697. return;
  2698. }
  2699. if (qs('.mgp_optionsMenu')) {
  2700. dom.cl.remove('.mgp_optionsMenu', ['mgp_visible', 'mgp_level2']);
  2701. }
  2702. return;
  2703. }
  2704. dom.cl.add('.mgp_contextMenu', 'mgp_hidden');
  2705. }
  2706. });
  2707. const writeClipboard = async (txt) => {
  2708. try {
  2709. await navigator.clipboard.writeText(txt);
  2710. } catch (ex) {
  2711. err(`[Clipboard] Failed to copy: ${ex}`);
  2712. if (isGM) {
  2713. if (isFN(GM.setClipboard)) {
  2714. return GM.setClipboard(txt);
  2715. } else if (isFN(GM_setClipboard)) {
  2716. return GM_setClipboard(txt);
  2717. }
  2718. }
  2719. }
  2720. };
  2721. const cleanURL = (str = '') => {
  2722. const invalid_chars = {
  2723. '\\': '\',
  2724. '\\/': '/',
  2725. '\\|': '|',
  2726. '<': '<',
  2727. '>': '>',
  2728. ':': ':',
  2729. '*': '*',
  2730. '?': '?',
  2731. '"': '"',
  2732. '🔞': '',
  2733. '#': ''
  2734. };
  2735. return str.replace(/[\\\\/\\|<>\\*\\?:#']/g, (v) => invalid_chars[v]);
  2736. };
  2737. const sortVideos = (ma, mb) => {
  2738. const a = +(/\d+(?=P|p\.)/.exec(ma) ?? ['0', '0'])[0];
  2739. const b = +(/\d+(?=P|p\.)/.exec(mb) ?? ['0', '0'])[0];
  2740. return b - a;
  2741. };
  2742. class mphHLS {
  2743. constructor(url = 'about:blank', data = {}, $el) {
  2744. this.url = url;
  2745. this.baseURL = new URL(this.url);
  2746. this.base = this.baseURL.origin + this.baseURL.pathname.replace(/\/(hls|master).m3u8/, '');
  2747. this.data = data;
  2748. this.$el = $el;
  2749. this.blobQualities = [];
  2750. this.resolutions = [];
  2751. }
  2752.  
  2753. msg(text = '') {
  2754. if (!this.$el) {
  2755. return;
  2756. }
  2757. dom.prop(this.$el, 'innerHTML', text);
  2758. }
  2759.  
  2760. static fixURL(str = '') {
  2761. return str.replace(/m3u8\/\//g, 'm3u8/');
  2762. }
  2763.  
  2764. updateBase(str = '') {
  2765. this.baseURL = new URL(str);
  2766. this.base = this.baseURL.origin + this.baseURL.pathname.replace(/\/(hls|master).m3u8/, '');
  2767. return this.base;
  2768. }
  2769.  
  2770. async build(arr = []) {
  2771. const hlsRes = await this.req(this.url).catch(err);
  2772. if (!hlsRes) {
  2773. return [];
  2774. }
  2775. const getFromTxt = (txt) => {
  2776. const resp = [];
  2777. for (const line of txt.split('\n')) {
  2778. // log('line', line);
  2779. if (line.startsWith('http')) {
  2780. resp.push(line);
  2781. } else if (line.match(/(hls|index)-.*?\.m3u8/g)) {
  2782. resp.push(line);
  2783. } else if (line.match(/d+\.mp4\.m3u8/g)) {
  2784. resp.push(line);
  2785. } else if (line.match(/\d+p\.h264\.mp4\.m3u8/g)) {
  2786. resp.push(line);
  2787. }
  2788. }
  2789. return resp;
  2790. };
  2791. if (hlsRes.startsWith('[{') && hlsRes.endsWith('}]')) {
  2792. const j = JSON.parse(hlsRes);
  2793. for (const e of j) {
  2794. if (e.format !== 'hls') {
  2795. continue;
  2796. }
  2797. if (e.defaultQuality) {
  2798. this.updateBase(e.videoUrl);
  2799. const vRes = await this.req(e.videoUrl).catch(err);
  2800. arr.push(...getFromTxt(vRes));
  2801. break;
  2802. }
  2803. }
  2804. } else {
  2805. arr.push(...getFromTxt(hlsRes));
  2806. }
  2807. if (arr.length === 0) {
  2808. err('Could not build');
  2809. return arr;
  2810. }
  2811. return arr.sort((ma, mb) => {
  2812. const a = +(/(\d+)p/.exec(ma) ?? ['0', '0'])[1];
  2813. const b = +(/(\d+)p/.exec(mb) ?? ['0', '0'])[1];
  2814. return b - a;
  2815. });
  2816. }
  2817.  
  2818. trimStr(str = '') {
  2819. return str.match(/[\w-]+\.\w{2,4}/) ? str.match(/[\w-]+\.\w{2,4}/)[0] : str;
  2820. }
  2821.  
  2822. async mergeRecords(res, frags = []) {
  2823. const fragRecords = [];
  2824. let frag;
  2825. for (const f of frags) {
  2826. this.msg(`[MagicPH] Merging segments for "${this.trimStr(res)}" (${this.trimStr(f)})`);
  2827. const s = f.includes('http') ? f : `${this.base}/${f}`;
  2828. if (/xhamster/.test(HP.current.root)) {
  2829. frag = await this.stream(s).catch(err);
  2830. } else {
  2831. frag = await Network.stream(s, null, this.data).catch(err);
  2832. }
  2833. if (frag === null) {
  2834. break;
  2835. }
  2836. fragRecords.push(frag);
  2837. }
  2838. return fragRecords;
  2839. }
  2840.  
  2841. async toBlob() {
  2842. try {
  2843. for (const res of this.resolutions) {
  2844. this.msg(`[MagicPH] Building cache for "${this.trimStr(res)}"`);
  2845. const u = res.startsWith('http') ? res : `${this.base}/${res}`;
  2846. const reqUrl = /xhamster/.test(HP.current.root) ? this.baseURL.origin + res : u;
  2847. const hlsVid = await this.req(reqUrl).catch(err);
  2848. let frags = [];
  2849. if (!hlsVid) {
  2850. break;
  2851. }
  2852. for (const line of hlsVid.split('\n')) {
  2853. if (line.match(/seg[\w-]+\.\w{2,4}/)) {
  2854. frags.push(line);
  2855. } else if (line.startsWith('http')) {
  2856. frags.push(line);
  2857. } else if (line.match(/hls-.*?\.ts/g)) {
  2858. frags.push(line);
  2859. } else if (line.match(/seg-.*?\.m4s/g)) {
  2860. frags.push(line);
  2861. } else if (line.match(/seg-.*?\.ts/g)) {
  2862. frags.push(line);
  2863. }
  2864. }
  2865. if (/xhamster/.test(HP.current.root)) {
  2866. frags = frags.map((f) => {
  2867. if (f.startsWith('/')) {
  2868. return this.baseURL.origin + f;
  2869. }
  2870. return f;
  2871. });
  2872. }
  2873. const fragRecords = await this.mergeRecords(res, frags);
  2874. if (fragRecords === null) {
  2875. return;
  2876. }
  2877. const blob = new Blob([arrayConcat(fragRecords)], {
  2878. type: 'application/octet-stream'
  2879. });
  2880. this.blobQualities.push(blob);
  2881. break; // We only want the highest quality
  2882. }
  2883. } catch (ex) {
  2884. err(ex);
  2885. }
  2886. return this.blobQualities;
  2887. }
  2888.  
  2889. async start() {
  2890. try {
  2891. info('[MagicPH] Creating cache using "Fetch API"');
  2892. this.msg('[MagicPH] Creating cache using "Fetch API"');
  2893. msg('[MagicPH] Creating cache using "Fetch API"', 2500);
  2894. this.resolutions = await this.build();
  2895. if (isBlank(this.resolutions)) {
  2896. this.msg('[MagicPH] Failed to cache, "resolutions" returned 0');
  2897. msg('[MagicPH] Failed to cache, "resolutions" returned 0', 2500);
  2898. return [];
  2899. }
  2900. const q = await this.toBlob();
  2901. this.msg('[MagicPH] Cache complete!');
  2902. msg('[MagicPH] Cache complete!', 2500);
  2903. return q;
  2904. } catch (ex) {
  2905. err(ex);
  2906. this.msg('[MagicPH] Error occured while creating cache');
  2907. msg('[MagicPH] Error occured while creating cache', 2500);
  2908. }
  2909. return [];
  2910. }
  2911.  
  2912. req(str, data) {
  2913. return Network.req(str, 'GET', 'text', data ?? this.data);
  2914. }
  2915.  
  2916. async stream(url = '') {
  2917. try {
  2918. const s = await Network.req(mphHLS.fixURL(url), 'GET', 'blob');
  2919. const ab = await s.arrayBuffer();
  2920. return new Uint8Array(ab);
  2921. } catch (ex) {
  2922. err(ex);
  2923. }
  2924. return null;
  2925. }
  2926. }
  2927. class mphMedia {
  2928. constructor(url, webpage) {
  2929. if (HP.webpage !== url) {
  2930. HP.current = url || HP.webpage.href || window.location;
  2931. }
  2932. this.webpage = webpage || HP.webpage.href;
  2933. this.mediaFiles = new Set();
  2934. this.playerId = undefined;
  2935. this.title = 'MagicPH';
  2936. }
  2937.  
  2938. async fetchQualities(mFiles = []) {
  2939. try {
  2940. mFiles = smToArr(mFiles);
  2941. return await new Promise((resolve, reject) => {
  2942. const testURL = mFiles;
  2943. const mf = mFiles.filter((file) => {
  2944. if (
  2945. /get_media\?s=/.test(file) ||
  2946. /media\/mp4\?s=/.test(file) ||
  2947. /youporn|tube8/gi.test(file)
  2948. ) {
  2949. return true;
  2950. }
  2951. return false;
  2952. });
  2953. if (isBlank(mf)) {
  2954. resolve(mFiles);
  2955. }
  2956. mFiles = [];
  2957. Network.req(mf)
  2958. .then((fQualites) => {
  2959. for (const item of fQualites) {
  2960. if (isEmpty(item.videoUrl) || Array.isArray(item.quality)) continue;
  2961. if (/\.mp4[.?]/g.test(item.videoUrl)) {
  2962. mFiles.push(item.videoUrl);
  2963. }
  2964. }
  2965. mFiles = mFiles.sort(sortVideos);
  2966. if (testURL[0].match(/thumbzilla/g)) {
  2967. mFiles.push(testURL[0]);
  2968. }
  2969. resolve(mFiles);
  2970. })
  2971. .catch(reject);
  2972. });
  2973. } catch (ex) {
  2974. err(ex);
  2975. return mFiles;
  2976. }
  2977. }
  2978.  
  2979. async mediaFinder(url, doc = document) {
  2980. let resp;
  2981. try {
  2982. const handleDoc = async (htmlDocument) => {
  2983. /** @type {HTMLElement} */
  2984. const selected = htmlDocument.documentElement;
  2985. if (/xhamster/.test(url)) {
  2986. for (const s of selected.getElementsByTagName('script')) {
  2987. if (isEmpty(s.innerHTML)) continue;
  2988. if (s.getAttribute('id') !== 'initials-script') continue;
  2989. const txt = s.innerHTML.toString();
  2990. const hlsReg = /"h264":\[{"url":"(.*?)"/.exec(txt);
  2991. if (hlsReg) {
  2992. tsSrc = hlsReg[1].replaceAll('\\', '');
  2993. }
  2994. const srcReg = /"mp4":{(.*?)}/.exec(txt);
  2995. if (!srcReg) {
  2996. continue;
  2997. }
  2998. const q = srcReg[1].match(/https:.*?p.h264.mp4/g);
  2999. if (!q) {
  3000. continue;
  3001. }
  3002. const titleReg = /"titleLocalized":"(.*?)"/.exec(txt);
  3003. if (titleReg) {
  3004. this.title = titleReg[1];
  3005. }
  3006. for (const src of q) {
  3007. this.mediaFiles.add(src.replaceAll('\\', ''));
  3008. }
  3009. }
  3010. return this.mediaFiles;
  3011. }
  3012. for (const s of selected.getElementsByTagName('script')) {
  3013. const txt = s.innerHTML;
  3014. const vtReg = /"video_title":"(.*?)",/.exec(txt);
  3015. if (vtReg) {
  3016. if (!Object.is(vtReg[1], this.title)) {
  3017. this.title = vtReg[1];
  3018. }
  3019. }
  3020. const embedIdReg = /{"embedId":(\d+)},/.exec(txt);
  3021. if (embedIdReg) {
  3022. if (!Object.is(embedIdReg[1], this.playerId)) {
  3023. this.playerId = embedIdReg[1];
  3024. }
  3025. }
  3026. const siteMedia = [
  3027. /(https:[\\/]+\D+4\?s=)+(\w|\d)+/g.test(txt),
  3028. /https:[\\/\w.]+tube8[\\/_.?=\w\d]+media[\\/_.?=\w\d]+/gi.test(txt),
  3029. /https:[\\/\w.]+thumbzilla[\\/_.?=\w\d]+media[\\/_.?=\w\d&]+/gi.test(txt),
  3030. /media_[\d]=+[\w\d\\*/+\s]+;/g.test(txt),
  3031. /https:[\\/\w.]+pornhub[\\/_.?=\w\d]+media[\\/_.?=\w\d&]+/gi.test(txt),
  3032. /mediaDefinition: (\[.*?\]),/g.test(txt)
  3033. ];
  3034. if (isEmpty(siteMedia.filter((m) => m))) continue;
  3035. const reg = /"format":"mp4",("remote":true,)?"videoUrl":"(.*?)"/.exec(txt);
  3036. if (!reg) {
  3037. continue;
  3038. }
  3039. const t = reg[2].replaceAll('\\', '');
  3040. let mediaTxt = t;
  3041. if (mediaTxt.startsWith('/')) {
  3042. const u = new URL(url);
  3043. mediaTxt = u.origin + t;
  3044. }
  3045. const resp = await Network.req(mediaTxt.replaceAll('\\', ''));
  3046. for (const v of resp.map((i) => i.videoUrl)) {
  3047. if (this.mediaFiles.has(v)) {
  3048. continue;
  3049. }
  3050. this.mediaFiles.add(v);
  3051. }
  3052. break;
  3053. }
  3054. if (this.mediaFiles.size === 0) {
  3055. const txt = selected.innerHTML.toString();
  3056. const vtReg = /video_title: '(.*?)'/.exec(txt);
  3057. if (vtReg) {
  3058. if (!Object.is(vtReg[1], this.title)) {
  3059. this.title = vtReg[1];
  3060. }
  3061. }
  3062. if (!/porntrex/.test(url)) {
  3063. const vUrlReg = txt.match(/video_alt_url: '(.*?)'/g);
  3064. if (vUrlReg) {
  3065. for (const r of vUrlReg) {
  3066. const v = /video_alt_url: '(.*?)'/.exec(r);
  3067. if (!v) {
  3068. continue;
  3069. }
  3070. const f = v[1].replaceAll('function/0/', '');
  3071. if (this.mediaFiles.has(f)) {
  3072. continue;
  3073. }
  3074. this.mediaFiles.add(f);
  3075. }
  3076. }
  3077. }
  3078. const ptReg = txt.match(/video_alt_url\d+: '(.*?)'/g);
  3079. if (ptReg) {
  3080. for (const r of ptReg) {
  3081. const v = /video_alt_url\d+: '(.*?)'/.exec(r);
  3082. if (!v) {
  3083. continue;
  3084. }
  3085. const f = v[1].replaceAll('function/0/', '');
  3086. if (this.mediaFiles.has(f)) {
  3087. continue;
  3088. }
  3089. this.mediaFiles.add(f);
  3090. }
  3091. }
  3092. const vReg = /(\w+)\.replaceAll\("\w+",(\w+)\+"pubs\/"\+(\w+)\+"\/"\)/.exec(txt);
  3093. if (vReg) {
  3094. const v = new RegExp(`${vReg[1]}="(<video.+</video>)"`).exec(txt);
  3095. if (v) {
  3096. let a, b, c;
  3097. a = b = c = '';
  3098. a = v[1];
  3099. for (const e of [vReg[2], vReg[3]]) {
  3100. const r = new RegExp(`${e}="(.*?)"`);
  3101. const eReg = r.exec(txt);
  3102. if (!eReg) {
  3103. continue;
  3104. }
  3105. if (isBlank(b)) {
  3106. b = eReg[1];
  3107. } else if (isBlank(c)) {
  3108. c = eReg[1];
  3109. }
  3110. }
  3111. const final = a.replaceAll('nrpuv', b + 'pubs/' + c + '/').replaceAll('\\"', '"');
  3112. const elem = make('mph-fake');
  3113. elem.innerHTML = final;
  3114. for (const source of elem.firstElementChild.childNodes) {
  3115. if (this.mediaFiles.has(source.src)) {
  3116. continue;
  3117. }
  3118. this.mediaFiles.add(source.src);
  3119. }
  3120. elem.remove();
  3121. }
  3122. }
  3123. }
  3124. return this.mediaFiles;
  3125. };
  3126. if (Object.is(url, window.location.href)) {
  3127. resp = handleDoc(doc);
  3128. } else {
  3129. resp = handleDoc(await Network.req(url, 'GET', 'document'));
  3130. }
  3131. await resp;
  3132. } catch (ex) {
  3133. err(ex);
  3134. }
  3135. return resp;
  3136. }
  3137.  
  3138. async fetchVideo(url) {
  3139. url = url ?? this.webpage.href;
  3140. const mFiles = await this.mediaFinder(url);
  3141. for (const m of mFiles) {
  3142. if (this.mediaFiles.has(m)) {
  3143. continue;
  3144. }
  3145. this.mediaFiles.add(m);
  3146. }
  3147. if (this.mediaFiles.size === 1) {
  3148. const arr = await this.fetchQualities(this.mediaFiles);
  3149. return arr.sort(sortVideos);
  3150. }
  3151. return this.mediaFiles;
  3152. }
  3153.  
  3154. async autoStart(url) {
  3155. try {
  3156. this.cleanup();
  3157. const q = await this.fetchVideo(url || this.webpage);
  3158. this.mediaFiles = smToArr(q).sort(sortVideos);
  3159. } catch (ex) {
  3160. err(ex);
  3161. } finally {
  3162. this.cacheToDom();
  3163. }
  3164. return this.mediaFiles;
  3165. }
  3166.  
  3167. cleanup() {
  3168. if (isEmpty(HP.videoData)) {
  3169. return;
  3170. }
  3171. this.cacheToDom();
  3172.  
  3173. this.mediaFiles.clear();
  3174. this.playerId = undefined;
  3175. this.title = 'MagicPH';
  3176. }
  3177.  
  3178. cacheToDom() {
  3179. if (isEmpty(HP.videoData)) {
  3180. return;
  3181. }
  3182. if (isEmpty(this.playerId)) {
  3183. return;
  3184. }
  3185. const obj = {
  3186. [this.playerId]: {
  3187. mediaFiles: [...this.mediaFiles],
  3188. playerId: this.playerId,
  3189. title: this.title
  3190. }
  3191. };
  3192. HP.videoData = {
  3193. ...HP.videoData,
  3194. ...obj
  3195. };
  3196. return HP.Video;
  3197. }
  3198. }
  3199. const makeContainer = (q = [], data = {}) => {
  3200. if (isEmpty(q)) {
  3201. info('Empty quality list', q);
  3202. return;
  3203. }
  3204. const d = {
  3205. host: HP.webpage.host,
  3206. hermes: {},
  3207. ...data
  3208. };
  3209. log(d);
  3210. const def = {
  3211. mediaFiles: q,
  3212. cache: [],
  3213. rows: new Set(),
  3214. $elems: [],
  3215. hermes: d.hermes
  3216. };
  3217. const vt = d.title ?? getVidTitle() ?? HP.Video.title ?? document.title ?? 'MagicPH';
  3218. if (!tab.hasTab(HP.webpage.host)) {
  3219. tab.create(HP.webpage.host, vt);
  3220. }
  3221. if (hostCache.has(vt)) {
  3222. const hc = hostCache.get(vt);
  3223. hc.mediaFiles.push(...q);
  3224. hostCache.set(vt, hc);
  3225. } else if (vt && !hostCache.has(vt)) {
  3226. hostCache.set(vt, def);
  3227. }
  3228. info('Video Qualities', q);
  3229. const hc = hostCache.get(vt) ?? {};
  3230. const mphList = make('mph-list', 'mph-list', {
  3231. dataset: {
  3232. host: vt
  3233. }
  3234. });
  3235. const setRows = (parentElem, val, rows = ['source', 'copy', 'download', 'open', 'preview']) => {
  3236. if (hc.ts || d.ts) {
  3237. rows.push('loadTS');
  3238. }
  3239. const elem = {
  3240. copy: make('mph-a', '', {
  3241. title: i18n$('copy'),
  3242. innerHTML: `${iconSVG.load('copy')} ${i18n$('copy')}`,
  3243. dataset: {
  3244. command: 'copy',
  3245. webpage: val
  3246. }
  3247. }),
  3248. download: make('mph-a', '', {
  3249. title: i18n$('download'),
  3250. innerHTML: `${iconSVG.load('download')} ${i18n$('download')}`,
  3251. ...(d.download ?? {
  3252. dataset: {
  3253. command: 'download-video',
  3254. webpage: val
  3255. }
  3256. })
  3257. }),
  3258. loadTS: make('mph-a', 'mph-item', {
  3259. title: 'Get qualities from .TS file',
  3260. innerHTML: 'Get from HLS stream',
  3261. dataset: {
  3262. command: 'load-ts',
  3263. webpage: val
  3264. }
  3265. }),
  3266. open: make('mph-a', '', {
  3267. title: 'Open in new Tab',
  3268. innerHTML: `${iconSVG.load('open')} Open`,
  3269. dataset: {
  3270. command: 'open-tab',
  3271. webpage: val
  3272. }
  3273. }),
  3274. preview: make('mph-a', '', {
  3275. title: 'Preview',
  3276. innerHTML: `${iconSVG.load('video')} Preview`,
  3277. dataset: {
  3278. command: 'preview-video',
  3279. webpage: val
  3280. }
  3281. }),
  3282. source: make('label'),
  3283. title: make('mph-elem', '', {
  3284. title: vt,
  3285. innerHTML: vt
  3286. })
  3287. };
  3288. const inp = make('input', 'mphURL', {
  3289. value: val,
  3290. type: 'url',
  3291. size: '70'
  3292. });
  3293. dom.attr(inp, 'readonly', '');
  3294. elem.source.append(inp);
  3295. for (const r of rows) {
  3296. if (!elem[r]) {
  3297. continue;
  3298. }
  3299. if (r === 'loadTS' && parentElem.parentElement) {
  3300. if (qs('[data-command="load-ts"]', parentElem.parentElement)) {
  3301. continue;
  3302. }
  3303. parentElem.parentElement.prepend(elem[r]);
  3304. } else {
  3305. parentElem.append(elem[r]);
  3306. }
  3307. }
  3308. };
  3309. const rows = hc.rows ?? def.rows;
  3310. if (isEmpty(d.rows)) {
  3311. for (const r of ['source', 'copy', 'download', 'open', 'preview']) {
  3312. if (rows.has(r)) {
  3313. continue;
  3314. }
  3315. rows.add(r);
  3316. }
  3317. } else {
  3318. for (const r of d.rows) {
  3319. if (rows.has(r)) {
  3320. continue;
  3321. }
  3322. rows.add(r);
  3323. }
  3324. }
  3325. for (const v of q) {
  3326. if (videoCache.has(v)) {
  3327. continue;
  3328. }
  3329. const isStr = typeof v === 'string';
  3330. const val = isStr ? v : URL.createObjectURL(v);
  3331. const $el = d.$el ?? make('mph-elem', 'mph-item');
  3332. videoCache.set(val, { title: vt, d, isStr, $el });
  3333. if (!mphList.contains($el)) {
  3334. mphList.append($el);
  3335. hc.$elems.push($el);
  3336. }
  3337. if (isEmpty(d.rows)) {
  3338. setRows($el, val);
  3339. } else {
  3340. setRows($el, val, d.rows);
  3341. }
  3342. Object.assign(hc, {
  3343. title: vt,
  3344. data: d,
  3345. isStr,
  3346. rows
  3347. });
  3348. hc.cache.push(val);
  3349. hostCache.set(vt, hc);
  3350. }
  3351. dul.append(mphList);
  3352. HP.updateCounters(q.length, vt, d.host);
  3353. };
  3354. const getVidTitle = (pgUrl) => {
  3355. const cVid = HP.Video;
  3356. try {
  3357. if (!isEmpty(pgUrl)) {
  3358. HP.current = pgUrl;
  3359. }
  3360. const root = HP.current.root;
  3361. if (/pornhub/.test(root)) {
  3362. if (win.playerObjList) {
  3363. const playerObjList = win.playerObjList;
  3364. const embedId = playerObjList[Object.keys(playerObjList)[0]].flashvars.embedId;
  3365. const flashvars = win[`flashvars_${embedId}`];
  3366. cVid.title = flashvars.video_title ?? win.VIDEO_SHOW?.videoTitleOriginal;
  3367. if (!isBlank(flashvars.mediaDefinitions)) {
  3368. const md = flashvars.mediaDefinitions[0];
  3369. if (md) {
  3370. tsSrc = md.videoUrl;
  3371. }
  3372. }
  3373. } else if (win.MGP) {
  3374. const MGP = win.MGP;
  3375. cVid.title = MGP.players[Object.keys(MGP.players)].settings().mainRoll.title;
  3376. } else {
  3377. cVid.title = win.VIDEO_SHOW?.videoTitleOriginal;
  3378. }
  3379. } else if (/redtube|youporn|tube8/.test(root)) {
  3380. const video_player_setup = win.page_params.video_player_setup;
  3381. const playervars = /redtube/.test(root)
  3382. ? video_player_setup[Object.keys(video_player_setup)[0]].playervars
  3383. : video_player_setup.playervars;
  3384. cVid.title = playervars.video_title;
  3385. for (const m of playervars.mediaDefinitions) {
  3386. if (m.format !== 'hls') {
  3387. continue;
  3388. }
  3389. tsSrc = m.videoUrl.startsWith('/') ? HP.webpage.origin + m.videoUrl : m.videoUrl;
  3390. break;
  3391. }
  3392. } else if (/thumbzilla/.test(root)) {
  3393. const video_vars = win.video_vars;
  3394. cVid.title = video_vars.video_title;
  3395. for (const e of video_vars.mediaDefinitions) {
  3396. if (e.format !== 'hls') {
  3397. continue;
  3398. }
  3399. if (e.defaultQuality) {
  3400. tsSrc = e.videoUrl;
  3401. break;
  3402. }
  3403. }
  3404. }
  3405. } catch (ex) {
  3406. err(ex);
  3407. }
  3408. return cVid.title;
  3409. };
  3410. const geekVideos = async (doc = document, pgUrl) => {
  3411. try {
  3412. const loc = isEmpty(pgUrl) ? HP.webpage : strToURL(pgUrl);
  3413. const media = new mphMedia(loc);
  3414. const qualities = await media.autoStart();
  3415. if (isEmpty(qualities)) {
  3416. info('Empty quality list', qualities);
  3417. return;
  3418. }
  3419. const vt = media.title ?? getVidTitle(pgUrl) ?? doc.title;
  3420. makeContainer(qualities, {
  3421. ts: tsSrc,
  3422. title: vt
  3423. });
  3424. await query('.mgp_container');
  3425. let injInto = doc.documentElement;
  3426. if (isMobile) {
  3427. dom.cl.add(vidQuality, 'mgp_selector');
  3428. if (qs('div.mgp_controls > div.mgp_qualitiesMenu') || /youporn/.test(loc.host)) {
  3429. info('Detected tablet...');
  3430. const vidFrame = await query('div.mgp_options');
  3431. if (!vidFrame.contains(vidQuality)) {
  3432. vidFrame.append(vidQuality);
  3433. }
  3434. dom.cl.add(vidQuality, 'mgp_optionsBtn');
  3435. dom.prop(vidQuality, 'innerHTML', iconSVG.mobileDownload);
  3436. return;
  3437. }
  3438. const injVid = qs('ul.mgp_switches') || qs('ul.mgp_optionsSwitches');
  3439. if (!injInto.contains(vidQuality)) {
  3440. injVid.prepend(vidQuality);
  3441. info('Detected mobile...');
  3442. dom.prop(
  3443. vidQuality,
  3444. 'innerHTML',
  3445. `${iconSVG.mobileDownload}<div class="mgp_value">Quality(s)</div>`
  3446. );
  3447. const cfgHeader = qs('.mgp_subPage') ? qs('.mgp_subPage').firstElementChild : null;
  3448. ael(qs('.mgp_options > .mgp_optionsBtn'), 'click', () => {
  3449. dom.prop(cfgHeader, 'innerHTML', 'Settings');
  3450. dom.cl.remove(qs('.mgp_optionsMenu'), 'mgp_level2');
  3451. });
  3452. ael(cfgHeader, 'click', () => {
  3453. dom.prop(cfgHeader, 'innerHTML', 'Settings');
  3454. dom.cl.remove(qs('.mgp_optionsMenu'), 'mgp_level2');
  3455. });
  3456. return;
  3457. }
  3458. }
  3459. if (qs('.mgp_contextMenu .mgp_content')) {
  3460. injInto = qs('.mgp_contextMenu .mgp_content');
  3461. } else if (qs('.mgp_contextMenu .mgp_contextContent')) {
  3462. injInto = qs('.mgp_contextMenu .mgp_contextContent');
  3463. }
  3464. if (!injInto.contains(vidQuality)) {
  3465. info('Detected desktop...');
  3466. injInto.prepend(vidQuality);
  3467. }
  3468. } catch (ex) {
  3469. err(ex);
  3470. }
  3471. };
  3472. const geekGifs = async (doc = document) => {
  3473. const qualities = [];
  3474. try {
  3475. let vt;
  3476. for (const s of doc.getElementsByTagName('script')) {
  3477. if (isEmpty(s.innerHTML)) continue;
  3478. if (s.getAttribute('type') !== 'application/ld+json') continue;
  3479. const txt = s.innerHTML.toString();
  3480. const j = JSON.parse(txt);
  3481. if (isEmpty(vt) && j.name) {
  3482. vt = j.name;
  3483. }
  3484. if (j.contentUrl) {
  3485. qualities.push(j.contentUrl);
  3486. } else if (j.thumbnailUrl) {
  3487. qualities.push(j.thumbnailUrl);
  3488. }
  3489. }
  3490. if (isEmpty(qualities)) {
  3491. info('Empty quality list', qualities);
  3492. return;
  3493. }
  3494. makeContainer(qualities, {
  3495. rows: ['source', 'copy', 'download', 'open'],
  3496. title: vt
  3497. });
  3498. let injInto = doc.documentElement;
  3499. if (qs('[id="js-gifWebMWrapper"]')) {
  3500. injInto = qs('[id="js-gifWebMWrapper"]').parentElement;
  3501. }
  3502. injInto.prepend(vidQuality);
  3503. } catch (ex) {
  3504. err(ex);
  3505. }
  3506. };
  3507. const geekShorts = async (doc = document) => {
  3508. try {
  3509. const cache = [];
  3510. let currentPage = 1;
  3511. let direction = '';
  3512. if (HP.host.includes('pornhub')) {
  3513. const requestToken = qs('.slider-container').dataset.token;
  3514. const makeData = async (JSON_SHORTIES) => {
  3515. for (const s of normalizeTarget(JSON_SHORTIES)) {
  3516. const media = s.mediaDefinitions.filter((i) => i.format === 'mp4');
  3517. const q = await Network.req(media[0].videoUrl);
  3518. if (!Array.isArray(q)) {
  3519. continue;
  3520. }
  3521. const qualities = q
  3522. .map((i) => i.videoUrl)
  3523. .filter((i) => !cache.includes(i))
  3524. .sort(sortVideos);
  3525. if (isEmpty(qualities)) {
  3526. continue;
  3527. }
  3528. cache.push(...qualities);
  3529. makeContainer(qualities, {
  3530. title: s.videoTitle,
  3531. rows: ['title', 'source', 'copy', 'download', 'open']
  3532. });
  3533. }
  3534. };
  3535. const loadPage = async (page = 1) => {
  3536. let JSON_SHORTIES = win.JSON_SHORTIES ?? [];
  3537. if (page > 1) {
  3538. const sURL =
  3539. '/shorties/get?' +
  3540. new URLSearchParams({
  3541. token: requestToken,
  3542. page
  3543. });
  3544. JSON_SHORTIES = await Network.req(sURL);
  3545. }
  3546. await makeData(JSON_SHORTIES);
  3547. };
  3548. let t = window.scrollY;
  3549. window.addEventListener('scroll', async () => {
  3550. const { scrollTop: e, scrollHeight: h, clientHeight: a } = doc.documentElement;
  3551. if (h - 5 <= e + a) {
  3552. currentPage += 1;
  3553. if (direction === 'next') {
  3554. await loadPage(currentPage);
  3555. }
  3556. }
  3557. if (t < window.scrollY) {
  3558. direction = 'next';
  3559. } else {
  3560. direction = 'previous';
  3561. }
  3562. t = window.scrollY;
  3563. });
  3564. await loadPage(currentPage);
  3565. }
  3566. let injInto = doc.documentElement;
  3567. if (qs('.wrapper > .container')) {
  3568. injInto = qs('.wrapper > .container');
  3569. }
  3570. injInto.prepend(vidQuality);
  3571. } catch (ex) {
  3572. err(ex);
  3573. }
  3574. };
  3575. const triggerHls = async (id) => {
  3576. msg('[MagicPH] Compiling hls video...', 1500);
  3577. if (!id) {
  3578. if (!qs('meta[name="twitter:image"]')) {
  3579. return;
  3580. }
  3581. const ma = qs('meta[name="twitter:image"]').content.match(/\/(\d+)\//);
  3582. id = ma ? ma[1] : location.pathname.replace(/\/-0/, '');
  3583. }
  3584. const { file } = await Network.req(`https://store.externulls.com/facts/file/${id}`);
  3585. const { hls_resources } = file;
  3586. const fl_cdn = hls_resources[Object.keys(hls_resources)[0]];
  3587. const hls = new mphHLS(`https://video.externulls.com/${fl_cdn}`);
  3588. const frags = await hls.build();
  3589. const m = await hls.mergeRecords(fl_cdn, frags);
  3590. const blob = new Blob([arrayConcat(m)], {
  3591. type: 'application/octet-stream'
  3592. });
  3593. makeContainer([blob], {
  3594. rows: ['title', 'download'],
  3595. title: file.data[0].cd_value
  3596. });
  3597. const injInto = qs('.XContentViewer__details__actions');
  3598. if (injInto) {
  3599. injInto.append(vidQuality);
  3600. }
  3601. };
  3602. // #region Site Director
  3603. const mainUserJS = async (doc = document) => {
  3604. try {
  3605. const { current } = HP;
  3606. const ignoreTags = new Set(['br', 'head', 'link', 'meta', 'script', 'style']);
  3607. /**
  3608. * @template { Function } F
  3609. * @param { (this: F, node: Node) => * } callback
  3610. */
  3611. const watch = (callback) => {
  3612. observe(doc, (mutations) => {
  3613. try {
  3614. for (const mutation of mutations) {
  3615. for (const node of mutation.addedNodes) {
  3616. if (node.nodeType !== 1) {
  3617. continue;
  3618. }
  3619. if (ignoreTags.has(node.localName)) {
  3620. continue;
  3621. }
  3622. if (node.parentElement === null) {
  3623. continue;
  3624. }
  3625. if (!(node instanceof HTMLElement)) {
  3626. continue;
  3627. }
  3628. callback(node);
  3629. }
  3630. }
  3631. } catch (ex) {
  3632. err(ex);
  3633. }
  3634. });
  3635. };
  3636. if (/onlyfans/.test(current.root)) {
  3637. const app = await query('[id="app"]');
  3638. while (isNull(app.__vue__)) {
  3639. await new Promise((resolve) => requestAnimationFrame(resolve));
  3640. }
  3641. const appVue = app.__vue__;
  3642. const videoPosts = new Set();
  3643. const ofMedia = new Set();
  3644. /**
  3645. * @param { any[] } postArr
  3646. */
  3647. const filterPosts = (postArr) => {
  3648. const media = postArr.filter((i) => {
  3649. if (isNull(i)) {
  3650. return false;
  3651. }
  3652. if (
  3653. normalizeTarget(i?.media).filter((m) => m.type === 'video' && (m.src || m.full))
  3654. .length === 0
  3655. ) {
  3656. return false;
  3657. }
  3658. return true;
  3659. });
  3660. if (media.length === 0) {
  3661. return;
  3662. }
  3663. const mediaObj = media.map((i) => {
  3664. const obj = {
  3665. ...i,
  3666. media: normalizeTarget(i.media)
  3667. .filter((m) => m.type === 'video')
  3668. .map((m) => {
  3669. return { ...m };
  3670. })
  3671. };
  3672. if (obj.chatId && ofUsers[obj.chatId]) {
  3673. obj.userData = ofUsers[obj.chatId];
  3674. } else if (obj.fromUser && ofUsers[obj.fromUser]) {
  3675. obj.userData = ofUsers[obj.fromUser];
  3676. } else if (obj.withUser && ofUsers[obj.withUser]) {
  3677. obj.userData = ofUsers[obj.withUser];
  3678. } else if (obj.author && ofUsers[obj.author]) {
  3679. obj.userData = ofUsers[obj.author];
  3680. }
  3681. return obj;
  3682. });
  3683. for (const p of mediaObj) {
  3684. if (videoPosts.has(p.id)) {
  3685. continue;
  3686. }
  3687. videoPosts.add(p.id);
  3688. if (!p.userData?.name) {
  3689. continue;
  3690. }
  3691. const userData = p.userData;
  3692. if (!videoCache.has(userData.name)) {
  3693. videoCache.set(userData.name, []);
  3694. }
  3695. const vid = videoCache.get(userData.name) || [];
  3696. if (vid.filter((v) => v.id === p.id).length !== 0) {
  3697. continue;
  3698. }
  3699. if (!hostCache.has(userData.name)) {
  3700. hostCache.set(userData.name, {
  3701. mediaFiles: [],
  3702. cache: [],
  3703. $elems: []
  3704. });
  3705. }
  3706. for (const m of normalizeTarget(p.media)) {
  3707. const videoId = `${m.id}`;
  3708. const createVideo = () => {
  3709. const vidObj = {
  3710. poster: m.thumb,
  3711. quality: 'original',
  3712. src: m.full,
  3713. title: videoId,
  3714. userId: userData.id,
  3715. username: userData.name,
  3716. name: userData.name,
  3717. user: userData,
  3718. duration: m.source.duration ? fancyTimeFormat(m.source.duration) : null,
  3719. frame: m.source.width ? ` (${m.source.width}x${m.source.height})` : null,
  3720. original:
  3721. p.responseType === 'post'
  3722. ? `/${p.id}/${userData.username}`
  3723. : p.responseType === 'message'
  3724. ? `/my/chats/chat/${userData.id}/`
  3725. : `/${p.id}`,
  3726. text: p.text ?? null,
  3727. raw: p
  3728. };
  3729. if (vidObj.src) {
  3730. vidObj.src = m.full;
  3731. if (ofMedia.has(vidObj)) {
  3732. return null;
  3733. }
  3734. ofMedia.add(vidObj);
  3735. vid.push(vidObj);
  3736. if (vidObj?.userId) {
  3737. for (const e of qsA(`mph-count[data-host="${vidObj.userId}"]`)) {
  3738. dom.text(e.parentElement, vidObj.userId);
  3739. }
  3740. }
  3741. } else if (m.videoSources) {
  3742. for (const [k, v] of Object.entries(m.videoSources)) {
  3743. if (isNull(v)) {
  3744. continue;
  3745. }
  3746. vidObj.quality = k;
  3747. vidObj.src = v;
  3748. if (ofMedia.has(vidObj)) {
  3749. return null;
  3750. }
  3751. ofMedia.add(vidObj);
  3752. vid.push(vidObj);
  3753. }
  3754. }
  3755. return vidObj;
  3756. };
  3757. const video = createVideo();
  3758. if (isNull(video)) {
  3759. continue;
  3760. }
  3761. }
  3762. if (vid.length !== 0) {
  3763. videoCache.set(userData.name, vid);
  3764. }
  3765. }
  3766. dbg('toGrab', { videoCache, ofMedia, hostCache });
  3767. addToWrapper();
  3768. };
  3769. appVue.$store.watch((a) => {
  3770. // dbg(a.router.route.to);
  3771. const userId = a.router.route.to.params?.userId ?? 'onlyfans';
  3772. if (userId !== currentUserId) {
  3773. currentUserId = userId;
  3774. }
  3775. if (a.users?.items) {
  3776. for (const c of Object.values(a.users.items)) {
  3777. if (!hostCache.has(c.name)) {
  3778. hostCache.set(c.name, {
  3779. mediaFiles: [],
  3780. cache: [],
  3781. $elems: []
  3782. });
  3783. }
  3784. if (ofUsers[c.id]) {
  3785. continue;
  3786. }
  3787. ofUsers[c.id] = c;
  3788. }
  3789. }
  3790. const toGrab = [];
  3791. if (a.posts?.items) {
  3792. toGrab.push(...Object.values(a.posts?.items));
  3793. }
  3794. if (a.chats?.messages) {
  3795. const messages = a.chats.messages;
  3796. if (a.chats?.items) {
  3797. for (const c of Object.values(a.chats.items)) {
  3798. for (const m of c.messages) {
  3799. if (!messages[m]) {
  3800. continue;
  3801. }
  3802. toGrab.push(messages[m]);
  3803. }
  3804. }
  3805. }
  3806. toGrab.push(...Object.values(messages));
  3807. }
  3808. if (toGrab.length !== 0) {
  3809. filterPosts(toGrab);
  3810. }
  3811. });
  3812. vueRouter = appVue._routerRoot['$options'].router.history;
  3813. } else if (/porntrex|analdin|porn00/.test(current.root)) {
  3814. if (/analdin/.test(current.root)) {
  3815. watch(async (node) => {
  3816. if (node.tagName === 'VIDEO') {
  3817. const media = new mphMedia(window.location.href);
  3818. const qualities = await media.autoStart();
  3819. const vt = media.title ?? doc.title;
  3820. makeContainer(qualities, {
  3821. ts: tsSrc,
  3822. title: vt
  3823. });
  3824. }
  3825. });
  3826. }
  3827. const media = new mphMedia(HP.webpage);
  3828. const qualities = await media.autoStart();
  3829. const vt = media.title ?? doc.title;
  3830. makeContainer(qualities, {
  3831. ts: tsSrc,
  3832. title: vt
  3833. });
  3834. } else if (/spankbang/.test(current.root)) {
  3835. const q = [];
  3836. for (const e of qsA('video > source')) {
  3837. if (typeof e.src === 'string') {
  3838. q.push(e.src);
  3839. }
  3840. }
  3841. makeContainer(q, {
  3842. ts: tsSrc,
  3843. title: cleanURL(doc.title)
  3844. });
  3845. } else if (/91porn/.test(current.root)) {
  3846. const s = qs('source', qs('video'));
  3847. if (!s) {
  3848. return;
  3849. }
  3850. if (typeof s.src === 'string') {
  3851. makeContainer([s.src], {
  3852. title: cleanURL(doc.title)
  3853. });
  3854. }
  3855. } else if (/hqporner/.test(current.root)) {
  3856. const elem = qs('.videoWrapper > iframe');
  3857. const media = new mphMedia(elem.src);
  3858. const qualities = await media.autoStart();
  3859. const vt = media.title ?? doc.title;
  3860. makeContainer(qualities, {
  3861. ts: tsSrc,
  3862. title: vt
  3863. });
  3864. } else if (/xnxx|xvideos/.test(current.root)) {
  3865. while (isNull(win.html5player)) {
  3866. await new Promise((resolve) => requestAnimationFrame(resolve));
  3867. }
  3868. if (!win.Hls.isSupported()) {
  3869. return;
  3870. }
  3871. const html5player = win.html5player;
  3872. const hls = html5player.hlsobj;
  3873. const h = new mphHLS(hls.url);
  3874. const q = await h.start();
  3875. makeContainer(q, {
  3876. rows: ['title', 'download'],
  3877. ts: tsSrc,
  3878. title: cleanURL(html5player.video_title ?? doc.title)
  3879. });
  3880. const injInto = /xvideos/.test(HP.current.root) ? qs('div.tabs') : qs('div.video-title');
  3881. if (injInto) {
  3882. injInto.append(vidQuality);
  3883. }
  3884. } else if (/xhamster/.test(current.root)) {
  3885. const media = new mphMedia(HP.webpage.href);
  3886. const qualities = await media.autoStart(HP.webpage.href);
  3887. const vt = media.title ?? doc.title;
  3888. makeContainer(qualities, {
  3889. hermes: {
  3890. credentials: 'omit',
  3891. referrer: `${HP.webpage.protocol}//${HP.host}`
  3892. },
  3893. ts: tsSrc,
  3894. title: vt
  3895. });
  3896. const menu = qs(isMobile ? '.xplayer-menu-mobile-bottom-left' : '.xp-context-menu');
  3897. if (menu) {
  3898. menu.prepend(vidQuality);
  3899. }
  3900. } else if (current.pathType === 'Video') {
  3901. await geekVideos(doc);
  3902. } else if (current.pathType === 'GIF') {
  3903. await geekGifs(doc);
  3904. } else if (current.pathType === 'Shorties') {
  3905. await geekShorts(doc);
  3906. } else if (/beeg/.test(current.root)) {
  3907. watch((node) => {
  3908. if (node.localName === 'div' && dom.cl.has(node, 'x-player__video')) {
  3909. const p = node.parentElement.parentElement;
  3910. if (qs('img[src]', p)) {
  3911. const ma = qs('img[src]', p).src.match(/\/(\d+)\//);
  3912. if (ma) {
  3913. triggerHls(ma[1]);
  3914. return;
  3915. }
  3916. }
  3917. triggerHls(location.pathname.replace(/\/-0/, ''));
  3918. }
  3919. });
  3920. } else if (/sxyprn/.test(current.root)) {
  3921. log(HP.current);
  3922. while (qs('#player_el').src === window.location.href) {
  3923. await new Promise((resolve) => requestAnimationFrame(resolve));
  3924. }
  3925. makeContainer([qs('#player_el').src], {
  3926. rows: ['source', 'copy', 'open', 'preview'],
  3927. ts: tsSrc,
  3928. title: dom.text(qs('.post_text')),
  3929. host: location.hostname
  3930. });
  3931. }
  3932. if (!/onlyfans/.test(current.root)) {
  3933. tab.create(HP.webpage.host);
  3934. }
  3935. } catch (ex) {
  3936. err(ex);
  3937. }
  3938. };
  3939. // #endregion
  3940. /**
  3941. * @template { Function } F
  3942. * @param { (this: F, doc: Document) => * } onDomReady
  3943. */
  3944. const loadDOM = (onDomReady) => {
  3945. if (isFN(onDomReady)) {
  3946. if (document.readyState === 'interactive' || document.readyState === 'complete') {
  3947. onDomReady(document);
  3948. } else {
  3949. document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), {
  3950. once: true
  3951. });
  3952. }
  3953. }
  3954. };
  3955. loadDOM((doc) => {
  3956. if (window.location === null) {
  3957. err('"window.location" is null, reload the webpage or use a different one');
  3958. return;
  3959. }
  3960. if (doc === null) {
  3961. err('"doc" is null, reload the webpage or use a different one');
  3962. return;
  3963. }
  3964. HP.inject(mainUserJS, doc);
  3965. });
  3966.  
  3967. })();