Motherless.com Improved

Infinite scroll (optional). Lazy loading. Filter by duration. Filter by phrases. Reveal all related galleries to video at desktop. Galleries and tags url rewritten and redirected to video/image section if available.

2024-05-04 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name         Motherless.com Improved
// @namespace    http://tampermonkey.net/
// @author       smartacephale
// @license MIT
// @version      2.0.1
// @description  Infinite scroll (optional). Lazy loading. Filter by duration. Filter by phrases. Reveal all related galleries to video at desktop. Galleries and tags url rewritten and redirected to video/image section if available.
// @match        https://motherless.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=motherless.com
// @grant        unsafeWindow
// @grant        GM_addStyle
// @run-at       document-idle
// @require      https://unpkg.com/[email protected]/dist/vue.global.prod.js
// ==/UserScript==
/* globals jQuery, $, Vue */

const LOGO = `
⡿⣹⡝⣯⡝⣯⡝⣯⠽⣭⢻⣭⢻⣭⢻⣭⢻⡭⢯⡽⡭⢏⡳⣍⡛⡜⡍⢎⡱⢊⠖⡱⢊⡖⣱⢊⠶⡱⢎⠶⣩⣿⢣⠝⣺⢿⣹⣷⣿⣿⣿⣿⢠⢃⠦⡑⢢⠜⣐⠢
⣟⡧⣟⢮⡽⣖⣻⢼⡻⣜⣳⢎⡷⣎⠷⣎⠷⣙⢧⡚⣥⢋⠶⣡⠞⣱⡘⣣⠱⣋⠼⡱⣉⠶⣡⡛⡼⣱⢫⡝⣶⣯⣏⢞⡥⢫⣝⣯⣟⣾⣿⣽⢂⠣⣌⡑⢣⡘⠤⣃
⣞⡷⣭⢟⡾⣹⢮⢷⣹⢧⣛⠮⣕⢎⡳⢬⠳⣍⠶⣙⢦⢋⡞⣥⢚⡥⣚⠴⣙⢦⠳⣥⢣⣛⡴⣯⢵⣣⢷⣹⣿⡷⣽⣎⣿⣧⢿⣯⣿⡿⣾⠏⢆⡓⢤⡉⢖⡨⡑⢆
⣷⡽⣺⣝⠾⣭⣛⣮⢷⣫⡽⣛⡼⣫⡝⣧⢻⣬⢳⢭⡲⣍⠶⣡⠏⡶⣹⡞⣵⢮⣟⡶⣯⣛⣾⡽⣷⡹⢎⣿⣿⣽⣷⣿⢿⣼⣻⣿⣿⢿⠏⡜⢢⢍⡒⠜⡢⡑⡜⢂
⡵⣹⠳⣞⣻⢧⠿⣜⣧⢯⣷⣯⢷⣳⣽⣚⠷⣎⡟⣮⢳⣎⢷⣣⣛⡴⢣⡜⣩⠝⣚⠿⡹⢭⢏⡿⣶⡹⡭⣿⣯⣿⣿⠿⣛⠻⢿⣿⣿⣿⡘⣌⢣⠒⣌⢣⠱⡑⣌⠣
⢫⡵⣛⡼⢣⡟⣯⢻⡼⢳⢮⡛⢿⢳⣟⡾⣯⢷⣹⣎⠷⣎⢧⡳⣍⡞⢧⡛⣖⢫⡜⢶⡱⣍⢮⡜⣡⢍⡱⣛⢭⡱⢦⡳⢬⣙⠶⣘⡛⢷⡘⢤⠣⢍⢆⢣⢣⠱⣌⠲
⣟⡴⣣⡝⢧⡝⢮⣛⡜⣣⢎⡽⣌⠧⣎⡹⢫⠿⣳⣯⣟⡾⣧⢷⣺⡜⣧⡽⣬⢳⠜⣣⠚⣌⠱⡌⡱⢊⠥⣉⠞⡹⢿⡝⢦⣽⢢⠅⣏⠻⡜⢢⡙⡌⢎⢆⢣⠓⠤⠓
⣯⣝⡳⣎⣗⡚⢧⡳⣜⡱⣎⠶⣭⢞⡶⠽⠧⠟⡶⢭⣻⡽⣯⣟⣳⣟⡷⢫⡱⣃⠞⡤⢋⠤⡓⢬⡑⣎⠶⣱⢮⡱⣣⣞⡧⣛⣬⣳⡌⢣⢍⠢⡑⢌⠢⠌⡂⠜⢠⠃
⡷⣎⢷⡹⣎⢿⣹⠷⣜⡱⣭⢟⡎⡞⡴⣉⠎⡵⡘⢦⢡⠹⣑⡛⢬⡳⣜⢣⠳⣥⢋⠶⣉⢖⣩⢒⡹⢌⠯⡝⢶⡿⣣⣗⡷⡽⣞⣳⣭⣳⠌⢆⡑⢢⠘⡄⠱⡈⢄⠂
⡿⣜⢧⡻⣜⢧⡻⣝⣮⢷⡘⢯⡜⡱⢜⢢⡙⠴⣉⢆⢣⡙⣤⠛⢦⣛⡬⢏⡷⢪⡝⢮⡱⢎⢆⠧⣘⠬⡒⣍⢲⡙⢷⣸⢞⡷⣯⡟⣯⢳⡿⢂⠜⡠⢃⠌⡱⠐⡌⢂
⣷⡹⣎⢷⡹⢎⣽⣋⢯⡹⣜⢣⡜⡱⢊⠦⡙⢦⡑⢎⠦⡱⢆⡛⢦⣛⡼⣫⢞⣧⣛⢮⡵⣋⠞⣬⢱⡊⡵⡘⢦⡙⠦⣍⢚⡼⣱⢏⡟⣫⢆⠱⡨⢐⠡⢊⠔⡡⠘⡄
⣳⢧⢻⡼⣳⡭⢶⡹⢮⡕⣎⠧⢎⡵⣉⠖⡩⢆⡹⢌⠶⣙⢬⡙⣦⢣⡟⣵⢯⣶⣛⡞⡶⣭⣛⡴⢣⠳⣥⠛⡴⣩⢓⢬⢚⡜⢣⢏⠼⣡⢎⡱⢢⢍⢢⠁⢎⠰⡁⠆
⡿⣜⣣⣽⢗⡻⢳⣹⢣⢞⡬⢳⣩⠒⣥⢚⡱⢊⡴⢋⡼⣘⢦⢻⡴⣻⣼⢯⡿⣾⡽⣹⡗⣧⢯⣜⢯⡳⣬⣛⡴⢣⢏⡞⡜⣬⢃⢎⠳⣌⢮⡱⢃⡎⢦⡙⢦⠑⡌⣂
⣾⡰⢧⣟⢮⢵⣫⢖⡏⣞⡜⣣⢖⡹⢤⠳⣘⢇⡞⡱⢎⡵⣋⢷⣹⢳⣞⣻⢽⣳⣟⣷⣻⡽⣞⡽⣎⢷⡳⣎⢷⢫⡞⡼⡱⢆⡫⣌⠳⡜⢦⡝⢣⠞⣢⡙⢆⢣⠒⡌
⣗⣯⡷⣹⢮⡳⣎⢷⡹⣎⡼⡱⢎⡵⣊⠷⣩⠞⡼⣱⢫⢶⡹⣎⢷⣫⢾⣭⢿⣳⣟⣾⣳⢿⡽⣯⡽⣣⢟⡼⣋⡧⣝⠶⣙⢮⡑⣎⢳⡙⣦⠍⣇⢫⠴⣙⠬⡒⢩⡐
⣿⢾⣟⡯⢷⣝⡮⢷⣹⢶⣙⢧⡻⣴⢋⡞⣥⢻⡜⣧⣛⣮⢷⣫⢷⣫⣟⡾⣯⢷⣞⡷⣿⣟⣿⣣⢟⡽⣎⢷⡹⢎⣧⢛⡜⡦⡝⣬⢣⡝⢦⢋⡔⢣⠚⡄⠓⡌⢅⠂
⣿⣻⡼⡽⣏⡾⣝⡿⣜⣧⢻⣎⡷⣭⡻⣜⣳⣏⣾⣳⡽⣞⣯⣳⢯⢶⣯⣽⣯⣟⣾⣻⣿⡽⣶⣛⢮⢳⡎⢷⣩⢏⠶⣩⢞⣱⡙⡦⢏⡼⣋⠖⣌⠣⢍⠢⡑⢌⣂⠣
⣷⣳⡽⣽⣫⣽⣻⣼⣻⣼⣻⢞⡷⣯⡽⣯⢷⣞⡷⣯⢿⣽⣞⡷⣯⣟⣾⣽⣾⣻⣾⣿⢯⣟⢶⡹⣎⣗⢺⢣⡞⡼⣩⣓⢮⢲⣍⡳⢏⡞⣥⢋⠤⢋⠬⡱⡘⠔⠢⡅
⣷⣻⢞⣷⣛⡾⣵⣳⣟⡾⣽⢯⣟⣷⣻⣽⣻⢾⣽⣟⣿⣻⣾⣿⣟⣾⣿⣽⣷⣿⣻⣯⣟⢮⡳⣝⠶⣪⢭⣓⢮⠵⡳⡜⣮⠳⣜⡝⣮⡝⡦⢽⣅⢋⢆⠱⣈⢎⡱⢄
⣷⢯⣟⡾⣽⣻⢷⣻⢾⡽⣯⣟⣾⣳⢟⡾⣽⣻⢾⡽⣞⡿⣽⣿⣿⣿⣾⣿⣯⣿⣿⣳⢏⡷⣝⢮⣛⣥⢳⣎⣏⢾⡱⣛⡴⡻⣜⡞⣵⢺⢩⠃⢏⡸⢌⢒⡡⢂⠖⣈
⣯⣟⡾⣽⣳⢯⡿⣽⢯⣟⣷⢻⣞⣭⢿⣹⠶⣏⡿⣽⢯⣟⡿⣿⣿⣿⣿⣿⣿⣿⢷⣯⣛⢾⡹⣎⠷⣎⢷⡺⣜⢧⣛⣥⢻⡵⣣⢟⡼⣋⠦⡙⠰⢂⠎⠢⠔⡡⢊⠄
⡿⣽⣻⢷⣯⠿⣽⣳⣟⡾⣞⠿⣼⢎⡷⣭⢻⡞⣽⣳⣟⡾⣿⣿⣿⣿⣽⡷⢾⣽⣻⢶⣫⣗⣻⣜⡻⣜⢧⡻⣜⢧⡻⣜⡳⣞⡵⣫⢞⣥⣶⣷⣿⣶⣿⣿⣿⣿⣿⣿`.trim();

//====================================================================================================

unsafeWindow.Vue = Vue;
const { ref, watch, reactive, createApp } = Vue;

class VueApp {
    template = `
    <div class="fixed bottom-0 right-0 z-9999 rounded px-2 py-0.5 bg-zinc-800 max-w-full m-2 py-1" v-if="state.uiEnabled">
    <div class="flex items-center cursor-pointer py-1 px-2 m-1" @click="state.hidden = !state.hidden">
      <span v-text="state.hidden ? '☒' : '⛶'" class="text-right w-full text-xl text-zinc-300"></span>
    </div>
    <template v-if="!state.hidden">
        <div class="flex items-center bg-zinc-900 py-1 px-2 m-1 font-mono">
          <input type="checkbox" id="exclude" v-model="state.filterNegative" class="mr-2 size-auto">
          <label for="exclude" class="text-zinc-300 font-mono">exclude</label>
          <input type="text" v-model="state.filterNegativeTags" placeholder="tag1, tag2,.." class="w-full h-8 text-white px-3 py-2 focus:outline-none bg-zinc-700 mx-2 rounded-sm">
        </div>
        <div class="flex items-center bg-zinc-900 py-1 px-2 m-1 font-mono">
          <input type="checkbox" id="include" v-model="state.filterPositive" class="mr-2 size-auto">
          <label for="include" class="text-zinc-300 font-mono">include</label>
          <input type="text" v-model="state.filterPositiveTags" placeholder="tag1, tag2,.." class="w-full h-8 text-white px-3 py-2 focus:outline-none bg-zinc-700 mx-2 rounded-sm size-auto">
        </div>
        <div class="flex items-center bg-zinc-900 py-1 px-2 m-1 font-mono" v-if="stateLocale.pagIndexLast > 1">
          <input type="checkbox" id="infiniteScrollEnabled" v-model="state.infiniteScrollEnabled" class="mr-2 size-auto">
          <label for="infiniteScrollEnabled" class="text-zinc-300 font-mono">infinite scroll enabled</label>
          <span class="text-zinc-300 ml-auto py-2">
            {{ stateLocale.pagIndexCur }}/{{ stateLocale.pagIndexLast }}
          </span>
        </div>
        <div class="flex items-center bg-zinc-900 py-1 px-2 m-1 font-mono">
          <input type="checkbox" id="duration" v-model="state.filterDuration" class="mr-2 size-auto">
          <label for="duration" class="text-zinc-300 font-mono">duration</label>
          <label for="durationFrom" class="text-zinc-300 mx-2 font-mono">from</label>
          <input type="number" step="10" min="0" max="72000" id="durationFrom" v-model.number="state.filterDurationFrom" class="w-24 h-8 bg-gray-700 text-zinc-300 rounded px-3 py-2 focus:outline-none">
          <label for="durationTo" class="text-zinc-300 mx-2 font-mono">to</label>
          <input type="number" step="10" min="0" max="72000" id="durationTo" v-model.number="state.filterDurationTo" class="w-24 h-8 bg-gray-700 text-zinc-300 rounded px-3 py-2 focus:outline-none">
        </div>
    </template>
  </div>
  `;

    constructor(state, stateLocale) {
        const root = parseDOM('<div id="tapermonkey-app" style="position: relative; z-index: 999999;"></div>');
        document.body.appendChild(root);

        this.vue = createApp({
            setup: () => ({ state, stateLocale }),
            template: this.template
        }).mount("#tapermonkey-app");

        GM_addStyle(`#tapermonkey-app *,#tapermonkey-app :before,#tapermonkey-app :after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}#tapermonkey-app :before,#tapermonkey-app :after{--tw-content: ""}#tapermonkey-app html,#tapermonkey-app :host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}#tapermonkey-app body{margin:0;line-height:inherit}#tapermonkey-app hr{height:0;color:inherit;border-top-width:1px}#tapermonkey-app abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}#tapermonkey-app h1,#tapermonkey-app h2,#tapermonkey-app h3,#tapermonkey-app h4,#tapermonkey-app h5,#tapermonkey-app h6{font-size:inherit;font-weight:inherit}#tapermonkey-app a{color:inherit;text-decoration:inherit}#tapermonkey-app b,#tapermonkey-app strong{font-weight:bolder}#tapermonkey-app code,#tapermonkey-app kbd,#tapermonkey-app samp,#tapermonkey-app pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}#tapermonkey-app small{font-size:80%}#tapermonkey-app sub,#tapermonkey-app sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}#tapermonkey-app sub{bottom:-.25em}#tapermonkey-app sup{top:-.5em}#tapermonkey-app table{text-indent:0;border-color:inherit;border-collapse:collapse}#tapermonkey-app button,#tapermonkey-app input,#tapermonkey-app optgroup,#tapermonkey-app select,#tapermonkey-app textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}#tapermonkey-app button,#tapermonkey-app select{text-transform:none}#tapermonkey-app button,#tapermonkey-app input:where([type=button]),#tapermonkey-app input:where([type=reset]),#tapermonkey-app input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}#tapermonkey-app :-moz-focusring{outline:auto}#tapermonkey-app :-moz-ui-invalid{box-shadow:none}#tapermonkey-app progress{vertical-align:baseline}#tapermonkey-app ::-webkit-inner-spin-button,#tapermonkey-app ::-webkit-outer-spin-button{height:auto}#tapermonkey-app [type=search]{-webkit-appearance:textfield;outline-offset:-2px}#tapermonkey-app ::-webkit-search-decoration{-webkit-appearance:none}#tapermonkey-app ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}#tapermonkey-app summary{display:list-item}#tapermonkey-app blockquote,#tapermonkey-app dl,#tapermonkey-app dd,#tapermonkey-app h1,#tapermonkey-app h2,#tapermonkey-app h3,#tapermonkey-app h4,#tapermonkey-app h5,#tapermonkey-app h6,#tapermonkey-app hr,#tapermonkey-app figure,#tapermonkey-app p,#tapermonkey-app pre{margin:0}#tapermonkey-app fieldset{margin:0;padding:0}#tapermonkey-app legend{padding:0}#tapermonkey-app ol,#tapermonkey-app ul,#tapermonkey-app menu{list-style:none;margin:0;padding:0}#tapermonkey-app dialog{padding:0}#tapermonkey-app textarea{resize:vertical}#tapermonkey-app input::-moz-placeholder,#tapermonkey-app textarea::-moz-placeholder{opacity:1;color:#9ca3af}#tapermonkey-app input::placeholder,#tapermonkey-app textarea::placeholder{opacity:1;color:#9ca3af}#tapermonkey-app button,#tapermonkey-app [role=button]{cursor:pointer}#tapermonkey-app :disabled{cursor:default}#tapermonkey-app img,#tapermonkey-app svg,#tapermonkey-app video,#tapermonkey-app canvas,#tapermonkey-app audio,#tapermonkey-app iframe,#tapermonkey-app embed,#tapermonkey-app object{display:block;vertical-align:middle}#tapermonkey-app img,#tapermonkey-app video{max-width:100%;height:auto}#tapermonkey-app [hidden]{display:none}#tapermonkey-app *,#tapermonkey-app :before,#tapermonkey-app :after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }#tapermonkey-app ::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }#tapermonkey-app .fixed{position:fixed}#tapermonkey-app .bottom-0{bottom:0}#tapermonkey-app .right-0{right:0}#tapermonkey-app .m-1{margin:.25rem}#tapermonkey-app .mx-2{margin-left:.5rem;margin-right:.5rem}#tapermonkey-app .ml-auto{margin-left:auto}#tapermonkey-app .mr-2{margin-right:.5rem}#tapermonkey-app .flex{display:flex}#tapermonkey-app .hidden{display:none}#tapermonkey-app .size-auto{width:auto;height:auto}#tapermonkey-app .h-8{height:2rem}#tapermonkey-app .w-24{width:6rem}#tapermonkey-app .w-full{width:100%}#tapermonkey-app .cursor-pointer{cursor:pointer}#tapermonkey-app .resize{resize:both}#tapermonkey-app .flex-wrap{flex-wrap:wrap}#tapermonkey-app .items-center{align-items:center}#tapermonkey-app .rounded{border-radius:.25rem}#tapermonkey-app .rounded-sm{border-radius:.125rem}#tapermonkey-app .border{border-width:1px}#tapermonkey-app .bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}#tapermonkey-app .bg-zinc-700{--tw-bg-opacity: 1;background-color:rgb(63 63 70 / var(--tw-bg-opacity))}#tapermonkey-app .bg-zinc-800{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity))}#tapermonkey-app .bg-zinc-900{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity))}#tapermonkey-app .px-2{padding-left:.5rem;padding-right:.5rem}#tapermonkey-app .px-3{padding-left:.75rem;padding-right:.75rem}#tapermonkey-app .py-0{padding-top:0;padding-bottom:0}#tapermonkey-app .py-0\.5{padding-top:.125rem;padding-bottom:.125rem}#tapermonkey-app .py-1{padding-top:.25rem;padding-bottom:.25rem}#tapermonkey-app .py-2{padding-top:.5rem;padding-bottom:.5rem}#tapermonkey-app .text-right{text-align:right}#tapermonkey-app .font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}#tapermonkey-app .text-xl{font-size:1.25rem;line-height:1.75rem}#tapermonkey-app .text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}#tapermonkey-app .text-zinc-300{--tw-text-opacity: 1;color:rgb(212 212 216 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}`);
    }
}

//====================================================================================================

const SCROLL_RESET_DELAY = 50;

//====================================================================================================

function $(x, e = document.body) { return e.querySelector(x); }

function parseDOM(html) {
    const parsed = new DOMParser().parseFromString(html, 'text/html').body;
    return parsed.children.length > 1 ? parsed : parsed.firstElementChild;
}

const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36';

function fetchCustomUA(url, ua = MOBILE_UA) {
    const headers = new Headers({ "User-Agent": ua });
    return fetch(url, { headers });
}

function fetchHtml(url) { return fetch(url).then((r) => r.text()).then((h) => parseDOM(h)); }

function fetchMobHtml(url) { return fetchCustomUA(url).then((r) => r.text()).then((h) => parseDOM(h)); }

function timeToSeconds(t) {
    return (t.match(/\d+/gm) || ['0'])
        .reverse()
        .map((s, i) => parseInt(s) * 60 ** i)
        .reduce((a, b) => a + b) || 0;
}

function stringToTags(s) {
    return s.split(",").map(s => s.trim().toLowerCase()).filter(_ => _);
}

function parseIntegerOr(n, or) {
    return Number.isInteger(parseInt(n)) ? parseInt(n) : or;
}

//====================================================================================================

class Observer {
    constructor(callback) {
        this.callback = callback;
        this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
    }

    observe(target) {
        this.observer.observe(target);
    }

    throttle(target, throttleTime) {
        this.observer.unobserve(target);
        setTimeout(() => this.observer.observe(target), throttleTime);
    }

    handleIntersection(entries) {
        for (const entry of entries) {
            if (entry.isIntersecting) {
                this.callback(entry.target);
            }
        }
    }

    static observeWhile(target, callback, throttleTime) {
        const observer_ = new Observer(async (target) => {
            const condition = await callback();
            if (condition) observer_.throttle(target, throttleTime);
        });
        observer_.observe(target);
        return observer_;
    }
}

//====================================================================================================

class MOTHERLESS_RULES {
    GALLERIES_TAG = '.media-related-galleries';
    GALLERY_TAG = '.gallery-container';
    GALLERY_MOB_TAG = '.ml-gallery-thumb';

    GROUP_TAG = '.group-minibio';

    constructor() {
        this.PAGINATION = $('.pagination_link');
        this.PAGINATION_LAST = parseInt(this.PAGINATION?.lastElementChild.previousElementSibling.innerText);
        this.CONTAINER = $('.content-inner');
    }

    GET_THUMBS(html) { return html.querySelectorAll('.thumb-container') }

    GET_THUMB_URL(thumb) { return thumb.firstElementChild.getAttribute('data-codename') };

    THUMB_DATA(thumb) {
        const title = (thumb.querySelector('.title')?.innerText || "").toLowerCase();
        const uploader = (thumb.querySelector('.uploader')?.innerText || "").toLowerCase();
        const duration = timeToSeconds(thumb.querySelector('.size')?.innerText || "0");
        return {
            title,
            uploader,
            duration
        }
    }

    URL_DATA() {
        const { origin, pathname, search, href } = window.location;
        const url = new URL(href);

        const offset = parseInt(url.searchParams.get('page')) || 1;
        const iteratable_url = (n) => {
            url.searchParams.set('page', n);
            return url.href;
        }

        return {
            offset,
            iteratable_url
        }
    }
}

const RULES = new MOTHERLESS_RULES();

//====================================================================================================

class Tick {
    constructor(interval){
        this.tick = null;
        this.interval = interval;
    }

    start(callback) {
        this.stop();
        this.tick = setInterval(callback, this.interval);
    }

    stop() {
        clearInterval(this.tick);
        this.tick = null;
    }
}

// reverse-engineering
function animate() {
    unsafeWindow.$("a, div, span, ul, li, p, button").off();

    const ANIMATION_INTERVAL = 500;
    const tick = new Tick(ANIMATION_INTERVAL);
    let container;

    function handleLeave(e) {
        tick.stop();
        const preview = e.target.querySelector('.static');
        unsafeWindow.$(preview.nextElementSibling).hide();
        preview.classList.remove('animating');
    }

    document.body.addEventListener('mouseover', (e) => {
        if (!(e.target.tagName === 'IMG' && e.target.classList.contains('static'))) return;
        if (e.target.classList.contains('animating')) return;

        e.target.classList.toggle('animating');

        container = e.target.parentElement.parentElement;
        container.addEventListener('mouseleave', handleLeave, {once: true});

        let g = true;
        let j = 0;
        let p = 0;
        const d = unsafeWindow.$(container.querySelector('.img-container'));

        const m = unsafeWindow.$(e.target.nextElementSibling || '<div style="z-index: 8; position: absolute; top: -11px;"></div>');
        if (!e.target.nextElementSibling) {
            unsafeWindow.$(e.target.parentElement).append(m);
        }

        const c = unsafeWindow.$(e.target);
        const h = e.target.getAttribute('data-strip-src');
        m.show();

        // t.trigger("resize"); u && u.slideDown(150), !b) {
        //var strip = unsafeWindow.$("<img>");
        //strip.load(function() {
        //    2500 !== this.naturalWidth && void 0 !== this.naturalWidth || (g = !0), b = !0
        //});
        //strip.attr("src", h);

        tick.start(function() {
            const v = Math.floor(1000.303 * c.width() / 100);
            const k = Math.floor(228.6666 * c.height() / 100);
            m.css("width", d.width());
            p ? m.css("height", "170px") : m.css("height", c.height() + "px");
            m.css("background-image", "url('" + h + "')");
            m.css("background-size", v + "px " + k + "px ");
            j * d.width() > v && (j = 0);
            m.css("background-position", j * d.width() + "px 0");
            j++;
        })
    });
}

//====================================================================================================

class PaginationPageManager {
    constructor() {
        handleLoadedHTML(document.body, RULES.CONTAINER);
        stateLocale.pagIndexLast = RULES.PAGINATION_LAST;
        this.paginationGenerator = this.createNextPageGenerator();
        this.paginationObserver = Observer.observeWhile(RULES.PAGINATION, this.generatorConsume, SCROLL_RESET_DELAY);
    }

    generatorConsume = async () => {
        if (!state.infiniteScrollEnabled) return;
        const {
            value: { url, offset } = {},
            done,
        } = this.paginationGenerator.next();
        if (!done) {
            console.log(url);
            const nextPageHTML = await fetchHtml(url);
            const prevScrollPos = document.documentElement.scrollTop;
            handleLoadedHTML(nextPageHTML, RULES.CONTAINER);
            stateLocale.pagIndexCur = offset;
            window.scrollTo(0, prevScrollPos);
        }
        return !this.generatorDone;
    }

    createNextPageGenerator() {
        const { offset, iteratable_url } = RULES.URL_DATA();
        stateLocale.pagIndexCur = offset;
        function* nextPageGenerator() {
            for (let c = offset + 1; c <= RULES.PAGINATION_LAST; c++) {
                const url = iteratable_url(c);
                yield { url, offset: c };
            }
        }
        return nextPageGenerator();
    }
}

//====================================================================================================

function fixGalleriesURLs() {
    const galleries = Array.from(document.querySelectorAll(('.gallery-container .info span:first-child')));
    galleries.forEach(s => {
        const videosCount = parseInt(s.innerText);
        const header = videosCount > 0 ? '/GV' : '/GI';
        const href = s.parentElement.parentElement.href.replace(/\/G/, () => header);
        s.parentElement.parentElement.href = href;
        s.parentElement.parentElement.parentElement.previousElementSibling.href = href;
    });
}

function fixHrefs() {
    document.querySelectorAll('a[href^="/term/"]').forEach(a => {
        a.href = a.href.replace(/[\w|+]+$/, (v) => `videos/${v}?term=${v}&range=0&size=0&sort=date`);
    });
}

//====================================================================================================

function displayAll() {
    unsafeWindow.$(RULES.GROUP_TAG).attr('style', 'display: block !important');
    unsafeWindow.$(RULES.GALLERY_TAG).attr('style', 'display: block !important');
}

function copyAttributes(target, source) {
    for (const attr of source.attributes) {
        target.setAttribute(attr.nodeName, attr.nodeValue);
    }
}

function replaceElementTag(e, tagName) {
    const newTagElement = document.createElement(tagName);
    copyAttributes(newTagElement, e);
    newTagElement.innerHTML = e.innerHTML;
    e.parentNode.replaceChild(newTagElement, e);
}

function mobileGalleryToDesktop(e) {
    e.firstElementChild.nextElementSibling.nextElementSibling.remove();
    e.firstElementChild.appendChild(e.firstElementChild.nextElementSibling);
    e.className = 'thumb-container gallery-container';
    e.firstElementChild.className = 'desktop-thumb image medium';
    e.firstElementChild.firstElementChild.nextElementSibling.className = 'gallery-captions';
    replaceElementTag(e.firstElementChild.firstElementChild, 'a');
}

async function desktopAddMobGalleries() {
    const galleries = $(RULES.GALLERIES_TAG);
    if (galleries) {
        const galleriesContainer = galleries.firstElementChild.nextElementSibling.firstElementChild;
        const galleriesCount = galleries.querySelectorAll(RULES.GALLERY_TAG).length;
        const mobDom = await fetchMobHtml(window.location.href);
        const mobGalleries = mobDom.querySelectorAll(RULES.GALLERY_MOB_TAG);
        for (const [i, x] of mobGalleries.entries()) {
            if (i > galleriesCount - 1) {
                mobileGalleryToDesktop(x);
                galleriesContainer.appendChild(x);
            }
        }
        displayAll();
    }
}

//====================================================================================================

class PersistentState {
    key = "state_2";

    constructor(state) {
        this.state = reactive(state);
        this.sync();
        this.watchPersistence();
    }

    sync() {
        this.trySetFromLocalStorage();
        window.addEventListener('focus', this.trySetFromLocalStorage);
    }

    watchPersistence() {
        watch(this.state, (value) => {
            this.saveToLocalStorage(this.key, value);
        });
    }

    saveToLocalStorage(key, value) {
        localStorage.setItem(key, JSON.stringify(value));
    }

    trySetFromLocalStorage = () => {
        const localStorageValue = localStorage.getItem(this.key);
        if (localStorageValue !== null) {
            const prevState = JSON.parse(localStorageValue);
            for (const prop of Object.keys(prevState)) {
                this.state[prop] = prevState[prop];
            }
        }
    }
}

//====================================================================================================

const { state } = new PersistentState({
    filterDurationFrom: 0,
    filterDurationTo: 600,
    filterDuration: false,
    filterNegativeTags: "",
    filterNegative: false,
    filterPositiveTags: "",
    filterPositive: false,
    infiniteScrollEnabled: true,
    uiEnabled: true,
});

const stateLocale = reactive({
    pagIndexLast: 1,
    pagIndexCur: 1,
});

watch([() => state.filterDurationFrom, () => state.filterDurationTo], (a, b) => {
    state.filterDurationFrom = parseIntegerOr(a[0], b[0]);
    state.filterDurationTo = parseIntegerOr(a[1], b[1]);
    if (state.filterDuration) filter_({ filterDuration: true });
});

watch(() => state.filterDuration, () => filter_({ filterDuration: true }));

watch(() => state.filterNegative, () => filter_({ filterNegative: true }));

watch(() => state.filterNegativeTags, () => {
    if (state.filterNegative) filter_({ filterNegative: true });
}, { deep: true });

watch(() => state.filterPositive, () => filter_({ filterPositive: true }));

watch(() => state.filterPositiveTags, () => {
    if (state.filterPositive) filter_({ filterPositive: true });
}, { deep: true });

//====================================================================================================

class LazyImgLoader {
    attributeName = 'lazy-loading';

    constructor(callback) {
        this.lazyImgObserver = new Observer((target) => {
            callback(target, this.delazify);
        });
    }

    lazify(parent, img) {
        img.setAttribute(this.attributeName, img.getAttribute('src'));
        img.src = '';
        this.lazyImgObserver.observe(parent);
    }

    delazify = (target) => {
        this.lazyImgObserver.observer.unobserve(target);
        const img = target.querySelector('.static');
        img.src = img.getAttribute(this.attributeName);
        img.removeAttribute(this.attributeName);
    }
}

//====================================================================================================

class DomManager {
    constructor() {
        this.data = new Map();
        this.lazyImgLoader = new LazyImgLoader((target, delazify) => {
            if (!this.isFiltered(target)) {
                delazify(target);
            }
        });
        this.addFilterStyles();
    }

    dataFilters = {
        filterDuration: new class {
            tag = 'filtered-duration';
            createFilter = () => {
                return (v) => {
                    const notInRange = v.duration < state.filterDurationFrom || v.duration > state.filterDurationTo;
                    return [this.tag, state.filterDuration && notInRange];
                }
            }
        },
        filterNegative: new class {
            tag = 'filtered-exclude';
            createFilter = () => {
                const tags = stringToTags(state.filterNegativeTags);
                return (v) => {
                    const containTags = tags.some(tag => v.title.includes(tag) || v.uploader.includes(tag));
                    return [this.tag, state.filterNegative && containTags];
                }
            }
        },
        filterPositive: new class {
            tag = 'filtered-include';
            createFilter = () => {
                const tags = stringToTags(state.filterPositiveTags);
                return (v) => {
                    const containTagsNot = tags.some(tag => !v.title.includes(tag) && !v.uploader.includes(tag));
                    return [this.tag, state.filterPositive && containTagsNot];
                }
            }
        }
    }

    addFilterStyles() {
        const tags = Object.keys(this.dataFilters).map(k => `.${this.dataFilters[k].tag}`).join(',');
        GM_addStyle(`${tags} { display: none !important; }`);
    }

    isFiltered(el) {
        return el.className.includes('filtered');
    }

    filter_ = (filters, offset = 0) => {
        const runFilters = [];

        for (const f of Object.keys(filters)) {
            runFilters.push(this.dataFilters[f].createFilter());
        }

        let offset_counter = 1;
        for (const v of this.data.values()) {
            offset_counter++;
            if (offset_counter > offset) {
                for (const rf of runFilters) {
                    const [tag, condition] = rf(v);
                    v.element.classList.toggle(tag, condition);
                }
                // jesus fucking christ for fuck sake mtfck bitch
                v.element.style.setProperty("display", this.isFiltered(v.element) ? 'none' : 'block', "important");
            }
        }
    }

    filterAll = (offset) => {
        const applyFilters = Object.assign({}, ...Object.keys(this.dataFilters).map(f => ({ [f]: state[f] })));
        this.filter_(applyFilters, offset);
    }

    handleLoadedHTML = (html, container) => {
        const thumbs = RULES.GET_THUMBS(html);

        const data_offset = this.data.size;

        for (const thumb of thumbs) {
            const url = RULES.GET_THUMB_URL(thumb);
            if (!url || this.data.has(url)) continue;

            const { title, uploader, duration } = RULES.THUMB_DATA(thumb);
            this.data.set(url, { element: thumb, duration, uploader, title });

            if (this.data.size > 1) {
                const img = thumb.querySelector('.static');
                this.lazyImgLoader.lazify(thumb, img);
            }

            container.appendChild(thumb);
        }

        this.filterAll(data_offset);
    }
}

const { filter_, handleLoadedHTML } = new DomManager();

GM_addStyle('.img-container, .desktop-thumb { min-height: 150px; max-height: 150px; }');

//====================================================================================================

console.clear();
console.log(LOGO);

desktopAddMobGalleries().then(() => fixGalleriesURLs());

if (RULES.PAGINATION) {
    const manager = new PaginationPageManager();
    animate();
}

if (RULES.PAGINATION ||
    /\/G(V|I)/.test(window.location.pathname) ||
    /t\=v/.test(window.location.search)) {
    if (!RULES.PAGINATION) handleLoadedHTML(document.body, RULES.CONTAINER);
    const ui = new VueApp(state, stateLocale);
}

fixHrefs();