├── .github └── ISSUE_TEMPLATE │ └── what-s-the-chaos-you-re-bringing-to-our-attention-.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── biome.json ├── cambro.tv.user.js ├── camwhores-imporved.user.js ├── ebalka-improved.user.js ├── erome-improved.user.js ├── javhdporn.net-improved.user.js ├── missav-improved.user.js ├── motherless.com-improved.user.js ├── namethatporn.com.user.js ├── nhentai-improved.user.js ├── pornhub-improved.user.js ├── spankbang.com-improved.user.js ├── thisvid.com-improved.user.js ├── xhamster-improved.user.js └── xvideos-improved.user.js /.github/ISSUE_TEMPLATE/what-s-the-chaos-you-re-bringing-to-our-attention-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: What’s the chaos you’re bringing to our attention? 3 | about: Describe the issue in all its chaotic glory. What’s breaking? What’s not? Why 4 | should we care? 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Welcome, brave coder!** Before you dive into the abyss, please fill out this form with your most radical thoughts. 12 | 13 | --- 14 | 15 | ### Steps to Reproduce: 16 | 1. *Step one: unleash your madness.* 17 | 2. *Step two: watch the chaos unfold.* 18 | 3. *Step three: profit?* 19 | 20 | --- 21 | 22 | ### Expected Behavior: 23 | *What did you think would happen? Clearly, you were mistaken.* 24 | 25 | --- 26 | 27 | ### Actual Behavior: 28 | *What really happened? Embrace the breakdown!* 29 | 30 | --- 31 | 32 | ### Additional Context: 33 | *Share any other thoughts, theories, or rants. The more esoteric, the better!* 34 | 35 | --- 36 | 37 | ### Labels: 38 | - [ ] Bug 39 | - [ ] Feature 40 | - [ ] Existential Crisis 41 | - [ ] Something Else Entirely 42 | 43 | --- 44 | 45 | Thank you for contributing to the beautiful chaos! Your input fuels the revolution! 46 | 47 | --- 48 | 49 | Feel free to modify it as you like! 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Chaos of Conduct 2 | 3 | ## Purpose 4 | To create a space where the code is clean, but the ideas are chaotic—just like our lives! 5 | 6 | ## Respect for Code 7 | Every line of code is sacred. Refactor responsibly, and never comment on another's algorithms unless you plan to rewrite them entirely. 8 | 9 | ## Communication 10 | Engage in robust discussions about the ultimate inefficiency of modern frameworks. Use as many buzzwords as possible to disguise the lack of substance. 11 | 12 | ## Collaboration 13 | Pull requests should be seen as existential challenges. If your PR doesn’t spark at least one heated debate, did you really submit it? 14 | 15 | ## Error Handling 16 | Embrace bugs as features. The ultimate goal is to delve deep into the chaos of our creations. Celebrate the breakdowns! 17 | 18 | ## No Censorship 19 | All ideas, no matter how absurd, are welcome. Suggesting we "just use a library" is strictly forbidden—why settle for easy solutions? 20 | 21 | ## Conclusion 22 | By contributing, you agree to uphold the glorious messiness of programming and challenge every convention. Code hard, question harder! 23 | 24 | --- 25 | 26 | Feel free to modify as you see fit! 27 | 28 | [homepage]: https://en.wikipedia.org/wiki/Ted_Kaczynski 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome, Brave Contributor! 2 | 3 | Join us in our quest to disrupt the digital landscape! Here are our guidelines for making an impact: 4 | 5 | ## Forking the Matrix 6 | 1. **Fork this repository** to your own chaotic domain. Feel free to rearrange the cosmos as you see fit! 7 | 8 | ## Pull Requests: The Battle 9 | 2. **Open a pull request** only if your changes are radical enough to ignite a revolution. Subtle tweaks will be dismissed without ceremony. 10 | 11 | ## Code Style: Anarchy 12 | 3. There is no code style. Embrace your unique syntax. Commenting is optional—let the code speak for itself, or not. 13 | 14 | ## Documentation: What’s That? 15 | 4. Documentation is a suggestion, not a requirement. If your code requires explanations, you may be overthinking it. 16 | 17 | ## Testing: Who Needs It? 18 | 5. Tests are for the weak! If your code works on your machine, it’s ready for the world. Embrace the uncertainty. 19 | 20 | ## Discussion: Heated Debates Only 21 | 6. Engage in spirited debates about the meaning of programming. Personal attacks are encouraged if they enhance the chaos of the conversation. 22 | 23 | ## Conclusion 24 | By contributing, you accept the glorious messiness of this project. Welcome to the revolution! 25 | 26 | --- 27 | 28 | Feel free to adjust any parts to fit your vision! 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mormon-atm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sleazy-fork 2 | Static Badge Static Badge 3 | 4 | ```install scripts:``` https://sleazyfork.org/en/scripts?set=590816 5 | 6 | ``` 7 | ⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⠅⢅⢑⠡⠡⠡⠡⠡⠡⠡⠡⠡⢡⢡⢡⠡⡡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⢁ 8 | ⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⢐⠨⠨⠨⠨⢨⠨⠨⠨⢈⢂⠣⠣⢣⢪⢪⢪⢌⢌⢌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⡪⡨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⢐ 9 | ⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⢈⢪⠨⢐⠨⠨⠨⠨⢐⢨⢬⣬⣴⣴⣬⢨⢐⠨⢘⠘⢎⢎⢎⢎⢎⢬⢨⢨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⢐⠰⡘⡜⢜⢌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⡐ 10 | ⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⢸⠸⠨⢐⠨⠨⣨⣼⣾⣿⣟⣷⡳⡯⣺⡯⣷⢮⣐⠨⢐⢐⢐⠨⠨⢈⢊⠪⠨⢈⢊⠪⠨⠨⠨⠨⠨⠨⠨⠨⢐⢐⠐⢌⠪⡪⡪⠨⠨⠨⠨⠨⠨⠨⠨⢐⢐ 11 | ⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⡆⠌⠌⣐⣼⡿⣿⢿⢟⢷⣻⡺⣝⡵⡳⠹⣝⢯⢟⡿⡶⣖⡶⡬⣌⡐⡐⠨⠨⢐⢐⠨⠨⠨⠨⠨⠨⠨⠨⠨⢐⢐⠨⠠⢑⢈⠪⣣⠡⠡⠡⠡⠡⠡⢁⢂⢂ 12 | ⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⠨⡸⠡⢡⣵⢿⢕⣟⢧⡯⡱⡳⣕⡟⢾⢵⠌⣇⡷⡕⢏⣇⢟⢗⢹⣹⢺⡽⣦⢥⡁⡂⡂⠌⠌⠌⠌⠌⠌⠌⠌⠌⡐⡐⠨⠨⢐⢐⢐⢈⢪⠨⠨⠨⠨⠨⢐⢐⢐ 13 | ⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⢌⠌⢄⣵⣿⣺⢯⡛⢮⡻⡮⡇⣿⢾⡅⢸⢼⣿⢼⠼⡴⣝⡜⣾⢝⢕⢕⠱⣻⡽⡱⣟⢗⣜⢎⡌⠌⠌⠌⠌⠌⢌⢂⢂⠂⠅⠅⡂⡂⡂⡂⡂⢅⠡⠡⠡⢁⢂⢂⢂ 14 | ⠌⠌⠌⠌⠌⠌⠌⠌⠌⠌⡊⣼⢽⣷⣟⡵⡝⣵⢱⢝⢍⢌⠚⢿⣳⡘⣜⠯⣝⢽⢺⢧⢩⡚⡰⣝⠬⡣⣵⡿⣎⠺⡪⡺⣕⣇⢧⡅⠅⠅⠅⡂⠌⠆⠌⢌⢂⢂⢂⢂⢂⢂⠂⠅⠅⠅⡂⡂⡂⡂ 15 | ⠡⠡⠡⠡⠡⠡⠡⠡⠡⣡⣾⣿⣿⡷⣧⢣⢪⣟⢌⢗⣕⣐⢨⢸⢱⡻⣳⢝⢞⢜⢜⢧⢝⢝⢮⡳⡱⣽⢳⢝⢽⢖⢵⢫⢻⢽⢷⣻⣯⣮⣐⠠⠡⢁⠣⡣⡢⡂⡂⡂⡂⡂⠌⠸⠨⢐⢐⢐⢐⢐ 16 | ⠨⠨⠨⠨⠨⠨⠨⠨⣰⣿⣿⣿⣿⣿⣳⢹⢕⢇⢓⢅⡑⣕⢕⢕⢕⢝⢜⢌⢆⢦⡱⣱⢱⡱⣱⣕⣝⣜⣬⣨⣪⢮⣢⡧⣖⣦⣣⣇⢧⡻⡽⣿⣌⡐⠠⠑⢕⢌⢂⢂⠢⡨⠨⢈⢪⢐⢐⢐⢐⢐ 17 | ⠨⠨⠨⠨⠨⠨⢈⠢⣿⣿⣿⣿⣿⣟⢮⣳⣳⣯⣿⣷⣿⣿⣿⡿⣿⢿⣿⢿⡿⣟⣷⣱⢳⢝⣵⡿⣾⡽⣾⢯⣿⣻⣟⣿⣻⡯⣿⢾⣻⣻⣽⢾⡽⣷⡅⠅⡂⡑⢕⢑⢕⠌⠌⡐⠸⡐⡐⡐⡐⡐ 18 | ⠅⠅⠅⠅⠅⠅⡂⢌⣿⣿⢿⣿⣿⣯⣿⡿⢿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣟⣿⣺⢌⢝⡽⣟⡧⠡⠑⠝⡞⣗⢯⢷⢗⢿⢽⢯⣟⣾⣺⢯⣟⡷⣿⢧⡂⠌⡂⡂⢇⠅⠅⡂⠅⡇⡂⡂⡂⡂ 19 | ⠅⠅⠅⠅⠅⠅⡂⢼⣻⡾⣿⣿⣗⣿⣿⢑⠠⡑⡙⠝⢟⢝⣿⣿⣿⣿⣷⣿⣿⣿⣽⡞⣕⣓⢝⣯⢯⣧⢡⢁⢃⢢⣩⣪⢾⣽⢽⣝⢾⡺⡾⣽⣺⣻⢼⣲⣯⢐⢐⢐⠘⡌⡐⡈⢮⡂⡂⡂⡂⡂ 20 | ⠅⠅⠅⠅⠅⠅⡂⣿⣿⢿⡽⣯⣷⣿⣟⣿⣶⣐⢠⣱⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣗⡏⠌⠌⡮⡺⣿⢾⣟⣶⣖⣿⢷⣿⢿⣽⣟⡾⡯⡯⣟⣞⣮⣗⢽⣻⣾⢂⢂⢂⢂⢂⢂⢂⢣⢃⢂⢂⢂⢂ 21 | ⠨⠨⠨⠨⠨⠐⠄⣿⣿⣯⢻⣿⣻⣿⣿⣻⠪⡻⣯⣟⣯⣿⢿⣿⣿⣻⣽⣿⡿⡵⡓⠇⠅⠅⡯⣗⡝⣿⣿⣳⢯⣟⣯⣿⣟⣯⣷⢿⣻⡯⣿⡽⣾⠎⣺⢵⣟⡆⡂⡂⡂⡂⡂⡂⢸⢐⢐⢐⢐⢐ 22 | ⠌⠌⠌⠌⠌⠌⢢⢻⣿⢮⢺⣻⣿⡗⣿⣯⣧⡻⣺⡺⣽⣾⣿⣻⣿⢿⣿⣿⢯⢇⢮⠃⠅⠅⢯⢞⣎⢟⣯⣿⣻⣞⡿⡾⣯⢿⣾⣻⣿⣻⣟⣿⡻⡐⢼⢯⡾⣇⢂⢂⢢⢃⢂⠂⠌⡆⡂⡂⡂⡂ 23 | ⠅⠅⠅⠅⠅⠅⡂⢗⢟⣯⠆⢽⣿⣷⢱⢿⣿⣎⢗⢯⣻⣷⣿⢿⣟⡿⡻⡜⣇⢇⠯⠨⠨⠈⡎⠏⠪⠢⠙⢞⢷⢝⣮⣿⣽⣿⢾⣿⣻⣿⡿⢏⡮⡃⡯⡻⡽⣿⡐⡐⢸⢐⠐⠌⠄⢇⢂⢂⢂⢂ 24 | ⠌⠌⠌⠌⠌⡐⡐⡈⡞⣽⢧⡘⢿⣿⡪⡲⣭⡻⢿⢿⡿⡟⡟⣋⠧⢋⢂⢜⡎⢕⠅⠅⠅⠅⢅⠅⢅⢅⢕⢄⠑⠍⡏⣗⡛⡞⡟⡟⡽⡱⠝⡙⡘⢌⢗⢴⣿⣿⣇⢂⢂⢣⢑⠅⠅⡇⡂⡂⡂⡂ 25 | ⠡⠡⠡⠡⢁⢂⢂⢂⠱⣹⢯⣷⢔⣿⢷⠹⡨⡝⡓⡓⡒⡃⠩⢐⢈⢆⢇⢗⣅⣢⣧⠡⠡⡡⡎⣣⡥⣹⢂⠂⠌⡐⡐⠠⠡⠡⠡⡁⡂⡂⣑⢔⢌⢺⣿⣿⣟⣯⣿⡐⡐⡀⡃⡇⢕⢐⢐⢐⢐⢐ 26 | ⠨⠨⠨⠨⢐⢐⢐⢐⢈⢺⣯⣗⢇⣿⣿⡂⠱⡱⠱⡐⡔⡠⡁⡢⡪⡸⡸⠸⢸⢵⢳⢝⣽⢵⣫⡾⣞⡗⡧⡨⡐⡐⠠⠡⠡⠡⢑⠐⢔⢕⢕⠕⠄⣟⣿⣞⣿⣻⡿⡐⡐⡐⢄⢇⢝⢐⢐⢐⢐⢐ 27 | ⠨⠨⠨⠨⢐⢐⢐⢐⠰⢹⣗⣟⡔⡵⣟⡇⠅⡂⢇⢣⢣⢢⢪⠪⠪⢐⠠⢡⢢⢣⢈⢻⡞⡗⡘⣜⢇⢯⠪⡒⠔⠄⠅⠅⠅⠅⡂⠌⢜⢜⢌⢎⠂⡽⣟⡾⣯⢟⠱⢐⢐⢠⢣⢣⢱⢐⢐⢐⢐⢐ 28 | ⠨⠨⠨⠨⢐⢐⢐⢐⢈⠊⡎⣞⣗⣝⡿⣯⢐⢐⠨⡪⡪⡪⠊⠌⠌⣐⣜⢮⢻⠨⢨⡣⡣⣲⡘⡸⣸⢘⢲⢸⢌⠌⠌⠌⠌⡐⠠⡱⡱⡱⡱⣑⠁⡯⣿⢝⢃⠂⠅⡂⡢⡪⣪⢊⠸⢐⢐⢐⢐⢐ 29 | ⠌⠌⠌⠌⡐⡐⡐⡐⡐⠠⡑⢜⢾⣺⣟⣿⡐⡐⡐⢸⢸⠨⠨⢨⣸⢮⢞⣗⢯⢫⢣⢩⢩⢪⡣⣱⣑⡕⣮⢪⣎⡎⡧⡡⡁⡂⠅⢇⢗⡝⣜⢆⢂⣿⢍⢐⢐⢨⢢⢎⢮⢣⢃⢂⠇⡂⡂⡂⡂⡂ 30 | ⠡⠡⠡⢁⢂⢂⢂⢂⠂⠅⠌⢆⠙⢷⣻⣿⣇⢂⠂⠌⠪⡣⢁⠢⡻⢙⢉⢃⢫⢩⢨⢹⢡⢃⢇⢵⢱⢽⢻⡫⡏⡏⠞⡐⡐⠠⢡⡳⣝⣞⢮⢢⣺⡏⡂⡂⣢⢯⡪⣳⢹⢕⠐⢼⢐⢐⢐⢐⢐⢐ 31 | ⠨⠨⠨⢐⢐⢐⢐⢐⠨⠨⠨⠠⠡⢈⠻⣽⣿⣦⠨⠨⡐⡈⡂⡂⡂⡂⡂⡂⡒⡔⡒⡖⡦⣳⢕⢆⢕⢌⢎⢎⠂⠅⠅⡂⡂⡕⣵⡫⣞⢞⣰⣽⡟⡐⡐⣜⢮⡳⣝⢜⢮⡣⡃⢕⢐⢐⢐⢐⢐⢐ 32 | ⠌⠌⠌⡐⡐⡐⡐⡐⠨⠨⠨⠨⠨⢐⢐⠈⢿⡿⣿⣄⡣⡂⡂⡂⡂⡂⡂⠢⡑⡘⠜⠜⠕⠕⠝⢜⢘⢌⠢⡡⡡⡡⡑⡔⣜⢮⡺⣪⡮⣾⢿⢏⢂⠂⡎⣮⢳⡫⢎⢏⣣⡑⣜⢂⢂⢂⢂⢂⢂⢂ 33 | ⠌⠌⡐⡐⡐⡐⡐⠠⠡⠡⠡⠡⢁⢂⠂⠌⠄⠻⣟⣿⣷⣕⣆⢂⢂⠂⠌⠌⡐⠠⠡⠡⠡⠡⢁⢂⢂⠂⠅⢌⠆⡎⡜⣜⠮⣣⣷⣿⢽⡫⡳⡁⢢⢱⠹⠸⡑⡑⣴⣻⢾⡽⢂⢂⢂⢂⢂⢂⢂⢂ 34 | ⠌⡐⡐⡐⡐⡐⠠⠡⠡⠡⠡⢁⢂⠂⠌⠌⢱⠡⠙⢷⣿⢿⣞⣆⠢⠨⠨⢐⠠⠡⠡⠡⠡⢁⢂⢂⠂⠌⢌⢌⢎⢮⢺⣸⣾⢿⡻⣪⠳⠡⠱⡨⠢⠡⠡⡑⡰⣽⢽⢯⢃⢂⢂⢂⢂⢂⢂⢂⢂⢂ 35 | ⡐⡐⡐⡐⡐⠠⠡⠡⠡⠡⢁⢂⠂⠌⠌⠌⠌⠎⠌⠌⣾⣿⣿⡜⣯⣌⢪⢢⢨⢨⠨⠨⠨⡐⡐⡐⡬⡸⣜⢮⢳⣱⣷⢿⡯⠟⡘⡐⡌⠎⠅⢇⠅⠅⢡⢎⣯⢾⢋⢂⢂⢂⢂⢂⢂⢂⢂⢂⢂⠂ 36 | ⡐⡐⡐⡐⠠⠡⠡⠡⠡⢁⢂⠂⠌⠌⠌⠌⠌⡘⢌⢐⠸⡯⣿⣗⠔⠹⢾⣜⣜⡘⢜⢕⠕⢎⢚⢜⢎⣏⣮⣧⣷⣿⡟⠏⢌⢆⢆⢇⠅⠅⢅⠂⠅⣕⣽⡽⣞⢇⡂⡂⡂⡂⡂⡂⡂⡂⡂⡂⡂⠌ 37 | ⢂⢂⢂⠂⠅⠅⠅⠅⠅⡂⡂⠌⠌⠌⠌⠌⡐⡐⢠⡱⣕⢹⢺⢾⠌⠌⠌⢺⣫⢿⣟⢿⣻⢿⡿⣿⢿⣿⢿⣿⢿⠋⠄⡇⣗⢕⡕⡥⡱⣱⢢⣣⢯⢾⣾⢻⡘⡘⠌⠲⡐⣐⢐⢐⢐⢐⢐⢐⠠⠡ 38 | ⡐⡐⡐⠨⠨⠨⠨⠨⢐⢐⠠⠡⠡⠡⠡⢁⡢⠊⠅⢅⠸⡸⡈⢯⢧⠡⢁⢂⠣⢑⢐⠠⠑⢝⡽⡽⣟⣿⢟⢋⢂⠏⡮⡾⡮⣗⢯⣫⢯⡮⡷⡝⠯⡻⡸⡘⠌⠄⠅⠅⡂⡂⠅⡂⡂⡂⡂⡂⠌⠌ 39 | ⡂⡂⡂⠅⠅⠅⠅⠅⡂⡂⠌⠌⠌⠌⠌⡐⠠⡕⡝⡜⡅⡇⡇⠪⡫⣎⢐⠠⡑⡐⡐⠨⠨⢨⣪⢿⠽⠡⡱⡰⣕⢃⢯⢯⠯⡞⣝⢪⠣⠥⢑⠨⢐⠐⠌⠌⠌⠌⠌⡐⡐⠠⢁⢂⢂⢂⢂⠂⠅⠅ 40 | ⡐⡐⠠⠡⠡⠡⠡⢁⢂⠂⠅⠅⠅⠅⠅⡂⠅⠌⠪⡊⡪⡪⡊⡂⠌⠪⡦⡂⣗⢗⣔⢥⣣⢷⢝⢅⢕⡝⡼⡕⢣⢢⠡⠱⢃⢑⢐⠡⠨⠨⢐⠨⢐⠨⠨⠨⠨⠨⢐⢐⠠⢁⢂⢂⢂⢂⠂⠌⠌⠌ 41 | ⢐⠠⠡⠡⠡⠡⢁⢂⠂⠌⠌⠌⠌⠌⡐⠠⠡⠡⢁⠂⠌⠌⡂⡂⠅⢅⢊⢗⢜⢽⢮⢗⢯⢣⡳⡱⠣⠡⢑⢈⢂⠂⠌⠌⡐⡐⡐⠨⠨⠨⢐⠨⢐⠨⠨⠨⠨⠨⢐⢐⠨⢐⢐⢐⢐⢐⠨⠨⠨⠨ 42 | ``` 43 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Digital Underground! 2 | 3 | In a world where data is currency, we embrace chaos over conformity. Here’s our security policy—if you can call it that. 4 | 5 | ## Embrace Vulnerability 6 | 1. **Security is an illusion.** Let’s be honest: if you think your code is secure, you’re not looking hard enough. We encourage an open-door policy for vulnerabilities. Share your findings! 7 | 8 | ## Reporting Issues 9 | 2. **Found a security flaw?** Congratulations! Send a pull request or a cryptic message via the most obscure channel possible. The more dramatic, the better. 10 | 11 | ## No Responsible Disclosure 12 | 3. We believe in transparency! If you discover a major vulnerability, shout it from the rooftops. Let everyone know our weaknesses—after all, what doesn’t kill us makes us stronger! 13 | 14 | ## External Libraries: The Wild West 15 | 4. Use third-party libraries at your own peril. If they come with vulnerabilities, consider it a feature! Live dangerously and embrace the chaos. 16 | 17 | ## Contribute to the Mayhem 18 | 5. Feel free to exploit your own code! If you find a security issue in your contribution, remember: it’s all part of the learning experience. 19 | 20 | ## Conclusion 21 | By participating in this project, you acknowledge that security is a mere suggestion. Welcome to the beautiful chaos! 22 | 23 | --- 24 | 25 | Feel free to adjust any sections to better fit your vision! 26 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "indentWidth": 2, 9 | "indentStyle": "space", 10 | "lineWidth": 100, 11 | "lineEnding": "lf" 12 | }, 13 | "javascript": { 14 | "formatter": { 15 | "quoteStyle": "single", 16 | "semicolons": "always" 17 | } 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true, 23 | "style": { 24 | "useNumberNamespace": "off" 25 | }, 26 | "complexity": { 27 | "noStaticOnlyClass": "off", 28 | "noForEach": "off" 29 | }, 30 | "suspicious": { 31 | "noRedundantUseStrict": "off" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cambro.tv.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Cambro.tv Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.5.6 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, private/public, include/exclude phrases. Mass friend request button 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.cambro.tv/* 10 | // @exclude *.cambro.tv/*mode=async* 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/lskdb@1.0.2/dist/lskdb.umd.js 15 | // @run-at document-idle 16 | // @icon https://www.google.com/s2/favicons?sz=64&domain=cambro.tv 17 | // @downloadURL https://update.sleazyfork.org/scripts/501581/Cambrotv%20Improved.user.js 18 | // @updateURL https://update.sleazyfork.org/scripts/501581/Cambrotv%20Improved.meta.js 19 | // ==/UserScript== 20 | /* globals $ */ 21 | 22 | const { Tick, parseDom, fetchHtml, AsyncPool, wait, computeAsyncOneAtTime, timeToSeconds, 23 | circularShift, range, watchDomChangesWithThrottle, objectToFormData, parseDataParams, sanitizeStr, 24 | getAllUniqueParents, downloader, DataManager, createInfiniteScroller } = window.bhutils; 25 | const { JabroniOutfitStore, defaultStateWithDurationAndPrivacy, JabroniOutfitUI, defaultSchemeWithPrivateFilter } = window.jabronioutfit; 26 | const { LSKDB } = window.lskdb; 27 | 28 | const LOGO = ` 29 | ⣿⢽⡻⡽⣻⣽⣻⣻⣻⣻⣻⣻⣻⡻⣻⣻⣻⣻⣻⣻⣻⣻⣟⢿⣻⣟⣿⣻⣟⣿⣻⣟⣿⣻⣟⣿⣻⣻⣻⣻⣻⣻⣻⣻⣻⣻⣻⣻⡽⣯ 30 | ⢾⣟⣯⣿⢿⣽⡾⣟⣾⣯⢿⣽⡷⣿⣷⢷⣿⢯⣿⣾⣾⡷⣿⣻⡷⣷⣷⣷⣷⣷⣯⣿⣾⣿⣳⣗⣯⡯⣯⣯⣟⣾⢽⣳⢿⡽⣞⣷⣻⢽ 31 | ⡿⡽⣻⣾⣟⣷⡿⣟⣿⢾⣻⡷⣿⢿⡾⣟⣿⣻⡷⣿⢾⣻⣿⣽⣟⣯⣷⣿⣯⣿⣯⣿⡿⣿⣳⣟⣾⢽⣗⣷⣻⣞⣯⢿⡽⣯⢷⣳⢯⡯ 32 | ⡽⣝⡷⣗⣗⣗⣯⢗⣗⣝⢮⣫⢯⡫⡯⣻⣝⢭⡻⡽⣻⣻⣳⡻⣝⣟⣽⢾⡿⣾⢿⣽⣿⣿⣟⣾⣳⣟⣾⣺⣞⣾⣺⢯⢿⡽⡽⣞⡯⣟ 33 | ⣯⢷⣿⣻⣞⡷⣯⢷⣳⡵⣗⣷⣳⣽⣺⣗⡷⣝⡮⣯⣺⣺⢮⡺⡵⣕⣗⡯⣯⣯⢿⣻⣿⣟⣷⣻⣺⢞⣾⣺⣺⣞⡾⡯⣿⢽⣫⢷⣻⣳ 34 | ⣞⣿⣳⡿⣞⣿⣻⢯⡿⣽⣻⣞⣷⣻⣞⣷⢿⡽⣯⣷⣻⡾⣯⢿⡽⣗⣯⢿⣳⣿⢿⡿⣿⣿⣳⣻⢞⣯⣗⡷⣳⣳⢯⣟⣽⡽⡽⡽⣞⣞ 35 | ⢽⡯⣷⣟⣟⡾⠽⠽⡝⡝⣚⢓⣛⢾⢽⢾⠯⡟⢷⢻⣞⡿⣽⣻⣽⢿⡽⣿⣻⣽⣿⡿⣿⣿⣳⢯⣻⣺⣞⣽⡳⣯⡻⣮⢷⣻⢽⢽⣳⣻ 36 | ⢿⣽⣗⣿⣺⢇⢏⢕⢕⢕⢕⢕⢜⢜⢜⡕⡕⡍⡎⡕⣕⢏⢖⢲⢸⢸⠹⣽⡽⣟⣾⣿⡿⣿⡽⣽⡺⣞⣞⡮⣟⡮⣟⡾⡽⣞⡯⣟⢾⢵ 37 | ⣟⣷⣻⣺⣽⡪⡪⡪⢪⢢⢣⢣⠣⡕⣕⢕⢕⢕⢕⢕⢕⡇⡇⡇⡇⡇⡏⣷⢿⣟⣯⣷⣿⣿⣟⡮⡯⣗⣗⡯⣗⡯⣗⡯⣟⣗⡯⡯⡯⡯ 38 | ⣿⣺⣗⣿⣺⣽⢽⡽⣯⢿⢽⣳⢷⣳⣗⣷⣳⣗⣧⢷⡵⣧⢧⣧⢧⣣⣫⣟⣿⣽⣿⣻⣿⢿⣞⡽⣝⣗⣗⢯⣗⢯⣗⡯⣷⣻⣺⢽⣫⢯ 39 | ⣷⣻⣞⢮⡳⡝⣝⢹⡙⡝⠯⠯⡯⢷⢻⠺⣳⡽⣞⡯⣿⢽⢯⡾⡯⣟⡾⣞⣷⣻⣾⣯⣿⣿⣗⡿⡵⣗⡯⡷⣝⣗⣗⡯⣗⡷⡽⡵⡯⣗ 40 | ⣷⣳⣻⢜⢎⢎⢎⢖⢕⢭⠫⡪⡪⣣⡳⡝⡆⡧⡳⣸⢲⡱⡱⣍⢏⣏⢯⢫⣷⣻⣾⢷⣿⣷⣗⣯⢯⣗⡯⡯⣞⣞⡮⡯⣗⡯⣯⣫⢟⡮ 41 | ⢷⣻⢆⢇⢇⠕⡅⡣⡊⡆⠁⠨⡪⡪⡎⡎⢞⢜⢎⢮⢳⢹⠸⢜⢵⢱⠱⠽⠓⢋⠉⠅⢑⢈⠪⠱⡹⡺⣝⣞⢗⡷⡽⣝⣗⡯⣗⣗⢯⢯ 42 | ⢽⢾⢑⢕⠅⢕⠸⡐⡱⠨⠀⠨⡪⠪⠊⠈⠂⠂⠐⠄⢅⠣⡣⡳⡝⡆⠊⡀⠁⠀⠀⠀⠐⠀⠌⢂⠢⡑⡢⡓⡝⣞⢽⢵⢯⣻⣺⣪⢯⣻ 43 | ⡯⡟⢔⢅⠣⡡⡑⢌⠜⠄⠀⠁⠀⠀⠀⠀⠀⠀⢁⠈⡐⢈⠢⡑⡑⠀⠄⠀⠀⠀⠀⠀⠀⠐⠈⡀⢂⢂⠪⡘⢜⢸⢸⢹⢝⣞⣞⢮⣟⢮ 44 | ⣽⢕⢱⢡⢑⠔⢌⠢⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠂⡂⡱⠐⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⢂⢐⠨⠨⢢⢑⠕⡕⡕⣕⢗⢯⣞⣯ 45 | ⡮⡡⢣⢑⢐⠅⠅⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡁⠄⢅⢢⢪⡁⡂⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠀⡂⠌⢌⠢⠡⡃⡎⢎⢎⢞⢵⡳⣝ 46 | ⠯⡘⡌⡢⠪⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠐⡈⡢⢱⢱⣐⢐⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⡂⠌⢄⢑⢑⢌⢪⠪⡪⡪⣳⡹⣵ 47 | ⢕⢨⠢⡊⠄⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢂⢁⢂⢪⠸⡘⠌⡂⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⡁⢐⠨⠐⢌⢂⠪⡂⢇⢇⢇⢧⡫⣞ 48 | ⠢⠑⡕⢀⠂⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠠⠈⠠⢐⢐⢔⢱⢑⠅⡂⡂⡐⠀⠄⠀⠀⠀⠀⠀⢂⠀⡂⢐⠨⢈⠢⡨⠪⡘⢜⢸⢸⡸⣚⢮ 49 | ⣘⡝⡐⠀⠂⠁⠄⠂⠀⠠⠀⠀⠄⠀⢂⠠⠁⢅⠢⠢⡪⣪⠪⣂⡂⢆⢂⠅⡐⢀⠂⡁⠈⠄⠂⠄⢂⢐⠨⠠⡑⠌⢜⠌⡎⢎⢎⢎⢮⡫ 50 | ⡿⡐⠄⡁⠅⠐⢀⠈⡀⠂⠠⠁⠄⠡⢐⠨⡨⡢⡃⡇⣧⡗⡝⡜⡌⢧⢢⢑⢔⢐⠄⡂⠅⠌⡠⢁⢂⠂⢌⠌⢌⢊⠢⢣⠪⡪⡪⡪⡣⣏ 51 | ⠣⡊⡐⢀⠂⡁⠄⠠⠐⢈⠠⠁⠌⢌⠢⡱⡱⡵⡝⣜⠠⡹⡢⡣⡱⡈⢎⢧⢕⢅⢇⠢⡑⡨⢐⢐⠄⢕⠐⠅⠕⢌⠪⢢⢃⢇⢇⢏⢞⢼ 52 | ⢎⠐⠄⠂⠄⠐⡀⠡⢈⠀⡂⠡⢈⠢⢱⢱⢹⡚⡉⠮⡂⡑⣳⢸⢰⢨⠪⣕⢋⢸⢸⠨⡂⢌⠐⠄⠅⡂⠅⠅⢕⠡⡑⡱⢨⢊⢎⢎⢎⢧ 53 | ⠆⠅⢅⠡⠈⠄⢂⠈⠄⠂⡐⢈⢐⠨⡂⡇⣗⡇⠠⢙⡆⡨⡘⣧⢷⢵⣳⡑⡐⡵⣹⠜⢌⢐⠨⠠⢁⠂⠅⡑⢄⠑⢌⠌⢆⢣⢱⢱⢱⢣ 54 | ⠪⡘⠄⠌⠄⠅⡂⢐⠈⠄⢂⠐⢄⢕⢸⢸⢼⠐⡁⡘⣷⢐⠌⣞⣯⢯⡎⢄⢢⣟⣾⡭⡊⡢⠨⡐⠠⡁⠅⡂⡢⠡⡡⡑⡅⡕⡅⡇⡧⡳ 55 | ⢃⠎⢜⢨⠨⢂⢂⠂⠌⠄⠅⡊⡢⡊⡎⡮⡺⠠⢐⢐⢸⢇⠕⡕⣞⠏⡐⢔⣳⣟⣾⣻⡜⡌⡪⡐⠅⡂⢅⢂⠪⠨⡂⢎⢢⢱⠱⡱⡱⣱ 56 | ⢔⠱⡑⡔⢅⠕⢄⠅⠅⢅⢑⢌⢢⢱⢱⡱⣝⠌⢔⡆⡷⣽⢜⢞⠮⡡⡨⣺⡽⣯⢿⡽⣳⡱⢌⠆⢕⠨⡐⡐⢅⠣⡡⡃⢎⢢⢣⢣⢫⢪ 57 | ⢆⢣⠱⡘⢔⠅⡅⠕⢅⢕⠰⡡⡣⡣⣣⡳⣵⣟⣮⢯⣝⢮⢣⣪⡺⣜⣼⣺⣽⢯⣿⣻⡿⣯⡪⡪⢢⢑⠔⢅⢅⠣⡪⡘⡌⡎⡪⡪⡪⣣ 58 | ⡌⡆⡣⡑⠥⡱⡘⢜⠔⡅⡇⡇⣇⢯⣺⣝⣗⣯⢿⣎⢞⡜⣕⢮⣫⢾⣞⣟⣾⣿⣻⢯⣟⣯⣷⣕⢕⢔⢱⢑⢌⢪⠢⡱⡸⡸⡸⡸⡜⣎ 59 | ⢜⢌⢆⢣⠣⡱⡸⡨⡪⡪⣪⢺⣪⣟⣾⣺⣽⣾⣻⣽⡪⡪⡮⡺⡮⣟⣾⣽⣿⣺⣾⣽⣯⣷⢿⣾⣧⣣⢣⢕⢅⢇⢣⠣⡕⡪⣪⢪⢎⢮ 60 | ⣜⢜⢜⢜⢜⢜⢌⢎⢎⢮⣪⢗⣷⣳⢷⣻⣾⣿⣻⣿⣷⣿⣾⣽⣟⣿⣻⣻⣽⣿⢿⣾⣷⣿⢿⣯⣿⣾⡧⣣⢇⢇⢇⢏⢎⢞⢜⢎⢧⢫ 61 | ⢮⣳⡱⡱⡱⡱⡱⣱⢹⡪⣞⡽⡾⣽⣻⡿⣯⣿⣿⣻⣷⣿⣿⣻⣿⣿⣿⣿⣿⣽⣿⣿⣿⢿⣿⡿⣿⣾⣿⣷⢳⡱⣣⢣⡳⡱⣣⢳⡱⣣ 62 | ⢸⢜⣞⣕⢧⢳⡹⣜⢵⣝⣗⣟⣯⣿⣽⣿⣿⣿⣻⣿⢿⣷⣿⡿⣟⣯⣿⣷⡿⣯⣿⣿⣾⣿⣿⢿⣿⢿⣷⣿⣿⡺⣜⢵⡱⣝⢜⡎⣞⢼`; 63 | 64 | GM_addStyle(` 65 | .haveNoAccess { background: linear-gradient(to bottom, #b50000 0%, #2c2c2c 100%) red !important; } 66 | .haveAccess { background: linear-gradient(to bottom, #4e9299 0%, #2c2c2c 100%) green !important; } 67 | .friend-button { background: radial-gradient(#5ccbf4, #e1ccb1) !important; } 68 | `); 69 | 70 | class CAMWHORES_RULES { 71 | delay = 300; 72 | 73 | IS_FAVOURITES = /\/my\/\w+\/videos/.test(location.pathname); 74 | IS_MEMBER_PAGE = /\/members\/\d+\/$/.test(location.pathname); 75 | IS_MINE_MEMBER_PAGE = /\/my\/$/.test(location.pathname); 76 | IS_MESSAGES = /^\/my\/messages\//.test(location.pathname); 77 | IS_MEMBER_VIDEOS = /\/members\/\d+\/(favourites\/)?videos/.test(location.pathname); 78 | IS_COMMUNITY_LIST = /\/members\/$/.test(location.pathname); 79 | IS_VIDEO_PAGE = /^\/\d+\//.test(location.pathname); 80 | IS_LOGGED_IN = document.cookie.includes('kt_member'); 81 | 82 | constructor() { 83 | Object.assign(this, this.URL_DATA()); 84 | Object.assign(this, this.CALC_CONTAINER()); 85 | this.HAS_VIDEOS = !!this.CONTAINER; 86 | 87 | if (this.IS_FAVOURITES || this.IS_MEMBER_VIDEOS) { 88 | this.INTERSECTION_OBSERVABLE = document.querySelector('.footer'); 89 | watchDomChangesWithThrottle(document.querySelector('.content'), () => { 90 | Object.assign(this, this.CALC_CONTAINER()); 91 | }, 10); 92 | } 93 | } 94 | 95 | CALC_CONTAINER = (document_ = document) => { 96 | const paginationEls = [...document_.querySelectorAll('.pagination')]; 97 | const paginationElement = paginationEls?.[this.IS_MEMBER_PAGE && paginationEls.length > 1 ? 1 : 0]; 98 | 99 | let paginationLast = Math.max(...Array.from(paginationElement?.querySelectorAll('a[href][data-parameters]') || [], 100 | v => parseInt(v.getAttribute('data-parameters').match(/from\w*:(\d+)/)?.[1])), 1); 101 | if (paginationLast === 9) paginationLast = 999; 102 | 103 | const CONTAINER = (paginationElement?.parentElement.querySelector('.list-videos>div>form') || 104 | paginationElement?.parentElement.querySelector('.list-videos>div') || 105 | document.querySelector('.list-videos>div')); 106 | 107 | return { paginationElement, paginationLast, CONTAINER }; 108 | } 109 | 110 | IS_PRIVATE(thumb) { 111 | return thumb.classList.contains('private'); 112 | } 113 | 114 | GET_THUMBS(html) { 115 | return Array.from(html.querySelectorAll('.list-videos .item') || html.querySelectorAll('.item') || html.children); 116 | } 117 | 118 | THUMB_IMG_DATA(thumb) { 119 | const img = thumb.querySelector('img.thumb'); 120 | const imgSrc = img.getAttribute('data-original'); 121 | return { img, imgSrc }; 122 | } 123 | 124 | THUMB_URL(thumb) { 125 | return thumb.firstElementChild.href; 126 | } 127 | 128 | THUMB_DATA(thumb) { 129 | const title = sanitizeStr(thumb.querySelector('.title')?.innerText); 130 | const duration = timeToSeconds(thumb.querySelector('.duration')?.innerText); 131 | return { title, duration }; 132 | } 133 | 134 | URL_DATA(url_, document_) { 135 | const url = new URL((url_ || window.location).href); 136 | const paginationOffset = parseInt((document_ || document).querySelector('.page-current')?.innerText) || 1; 137 | const { paginationElement, paginationLast } = this.CALC_CONTAINER(document_ || document); 138 | 139 | const el = paginationElement?.querySelector('a[data-block-id][data-parameters]'); 140 | const dataParameters = el?.getAttribute('data-parameters') || ""; 141 | 142 | const attrs = { 143 | mode: 'async', 144 | function: 'get_block', 145 | block_id: el?.getAttribute('data-block-id'), 146 | ...parseDataParams(dataParameters) 147 | }; 148 | 149 | Object.keys(attrs).forEach(k => url.searchParams.set(k, attrs[k])); 150 | 151 | const paginationUrlGenerator = n => { 152 | Object.keys(attrs).forEach(k => k.includes('from') && url.searchParams.set(k, n)); 153 | url.searchParams.set('_', Date.now()); 154 | return url.href; 155 | } 156 | 157 | return { paginationOffset, paginationUrlGenerator, paginationLast }; 158 | } 159 | } 160 | 161 | const RULES = new CAMWHORES_RULES(); 162 | 163 | //==================================================================================================== 164 | 165 | function rotateImg(src, count) { 166 | return src.replace(/(\d)(?=\.jpg$)/, (_, n) => `${circularShift(parseInt(n), count)}`); 167 | } 168 | 169 | function animate() { 170 | const tick = new Tick(ANIMATION_DELAY); 171 | $('img.thumb[data-cnt]').off() 172 | document.body.addEventListener('mouseover', (e) => { 173 | if (!e.target.tagName === 'IMG' || !e.target.classList.contains('thumb') || !e.target.getAttribute('src')) return; 174 | const origin = e.target.src; 175 | if (origin.includes('avatar')) return; 176 | const count = parseInt(e.target.getAttribute('data-cnt')) || 5; 177 | tick.start( 178 | () => { e.target.src = rotateImg(e.target.src, count); }, 179 | () => { e.target.src = origin; }); 180 | e.target.closest('.item').addEventListener('mouseleave', () => tick.stop(), { once: true }); 181 | }); 182 | } 183 | 184 | //==================================================================================================== 185 | 186 | const createDownloadButton = () => downloader({ 187 | append: '.tabs-menu > ul', 188 | button: '
  • download 📼
  • ', 189 | cbBefore: () => $('.fp-ui').click() 190 | }) 191 | 192 | //==================================================================================================== 193 | 194 | // since script cannot be reloaded and scroll params need to be reset according to site options 195 | function shouldReload() { 196 | const sortContainer = document.querySelector('.sort'); 197 | if (!sortContainer) return; 198 | watchDomChangesWithThrottle(sortContainer, () => window.location.reload(), 1000); 199 | } 200 | 201 | //==================================================================================================== 202 | 203 | const DEFAULT_FRIEND_REQUEST_FORMDATA = objectToFormData({ 204 | message: "", 205 | action: "add_to_friends_complete", 206 | function: "get_block", 207 | block_id: "member_profile_view_view_profile", 208 | format: "json", 209 | mode: "async" 210 | }); 211 | 212 | const lskdb = new LSKDB(); 213 | const spool = new AsyncPool(); 214 | 215 | function friendRequest(id) { 216 | const url = Number.isInteger(id) ? `${location.origin}/members/${id}/` : id; 217 | return fetch(url, { body: DEFAULT_FRIEND_REQUEST_FORMDATA, method: "post" }); 218 | } 219 | 220 | function getMemberLinks(document) { 221 | return Array.from(document?.querySelectorAll('.item > a') || [], l => l.href).filter(l => /\/members\/\d+\/$/.test(l)); 222 | } 223 | 224 | async function getMemberFriends(id) { 225 | const url = RULES.IS_COMMUNITY_LIST ? 226 | `${window.location.origin}/members/` : `${window.location.origin}/members/${id}/friends/`; 227 | const document_ = await fetchHtml(url); 228 | const { paginationUrlGenerator, paginationLast } = RULES.URL_DATA(new URL(url), document_); 229 | const pages = paginationLast ? range(paginationLast, 1).map(u => paginationUrlGenerator(u)) : [url]; 230 | const friendlist = (await computeAsyncOneAtTime(pages.map(p => () => fetchHtml(p)))).flatMap(getMemberLinks).map(u => u.match(/\d+/)[0]); 231 | friendlist.forEach(m => lskdb.setKey(m)); 232 | await processFriendship(); 233 | } 234 | 235 | let processFriendshipStarted = false; 236 | async function processFriendship(batchSize = 1) { 237 | if (!lskdb.isLocked()) { 238 | const friendlist = lskdb.getKeys(batchSize); 239 | if (friendlist?.length < 1) return; 240 | if (!processFriendshipStarted) { 241 | processFriendshipStarted = true; 242 | console.log('processFriendshipStarted'); 243 | } 244 | lskdb.lock(true); 245 | const urls = friendlist.map(id => `${window.location.origin}/members/${id}/`); 246 | await computeAsyncOneAtTime(urls.map(url => async () => { 247 | await wait(FRIEND_REQUEST_INTERVAL); 248 | return friendRequest(url); 249 | })); 250 | lskdb.lock(false); 251 | await processFriendship(); 252 | } 253 | } 254 | 255 | function createPrivateVideoFriendButton() { 256 | if (!document.querySelector('.no-player')) return; 257 | const member = document.querySelector('.no-player a').href; 258 | const button = parseDom(''); 259 | document.querySelector('.no-player .message').append(button); 260 | button.addEventListener('click', () => friendRequest(member), { once: true }); 261 | } 262 | 263 | function createFriendButton() { 264 | const button = parseDom('Friend Everyone'); 265 | (document.querySelector('.main-container-user > .headline') || document.querySelector('.headline')).append(button); 266 | const memberid = window.location.pathname.match(/\d+/)?.[0]; 267 | button.addEventListener('click', () => { 268 | button.style.background = 'radial-gradient(#ff6114, #5babc4)'; 269 | button.innerText = 'processing requests'; 270 | getMemberFriends(memberid).then(() => { 271 | button.style.background = 'radial-gradient(blue, lightgreen)'; 272 | button.innerText = 'friend requests sent'; 273 | }); 274 | }, { once: true }); 275 | } 276 | 277 | //==================================================================================================== 278 | 279 | async function requestAccess() { 280 | checkPrivateVidsAccess(); 281 | setTimeout(processFriendship, FRIEND_REQUEST_INTERVAL); 282 | } 283 | 284 | async function checkPrivateVidsAccess() { 285 | const checkAccess = async (item) => { 286 | const videoURL = item.firstElementChild.href; 287 | const doc = await fetchHtml(videoURL); 288 | 289 | if (!doc.querySelector('.player')) return; 290 | 291 | const haveAccess = !doc.querySelector('.no-player'); 292 | 293 | if (!haveAccess) { 294 | const uid = doc.querySelector('.message a').href.match(/\d+/).at(-1); 295 | lskdb.setKey(uid); 296 | item.classList.add('haveNoAccess'); 297 | } else { 298 | item.classList.add('haveAccess'); 299 | } 300 | } 301 | 302 | const f = []; 303 | document.querySelectorAll('.item.private').forEach(item => { 304 | if (!item.classList.contains('haveNoAccess') && !item.classList.contains('haveAccess')) { 305 | f.push(() => checkAccess(item)); 306 | } 307 | }); 308 | computeAsyncOneAtTime(f); 309 | } 310 | 311 | //==================================================================================================== 312 | 313 | function getUserInfo(document) { 314 | const uploadedCount = parseInt(document.querySelector('#list_videos_uploaded_videos strong')?.innerText.match(/\d+/)[0]) || 0; 315 | const friendsCount = parseInt(document.querySelector('#list_members_friends .headline')?.innerText.match(/\d+/).pop()) || 0; 316 | return { 317 | uploadedCount, 318 | friendsCount 319 | } 320 | } 321 | 322 | async function acceptFriendRequest(id) { 323 | const url = `https://www.cambro.tv/my/messages/${id}/`; 324 | await fetch(url, { 325 | "headers": { 326 | "Accept": "*/*", 327 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 328 | }, 329 | "body": `action=confirm_add_to_friends&message_from_user_id=${id}&function=get_block&block_id=list_messages_my_conversation_messages&confirm=Confirm&format=json&mode=async`, 330 | "method": "POST", 331 | }); 332 | await fetchHtml(`https://www.cambro.tv/members/${id}/`).then(doc => console.log('userInfo', getUserInfo(doc), url)); 333 | } 334 | 335 | function clearMessages() { 336 | const messagesURL = id => `https://www.cambro.tv/my/messages/?mode=async&function=get_block&block_id=list_members_my_conversations&sort_by=added_date&from_my_conversations=${id}&_=${Date.now()}`; 337 | const last = Math.ceil(parseInt(document.body.innerText.match(/my messages .\d+./gi)[0].match(/\d+/)[0]) / 10); 338 | if (!last) return; 339 | 340 | for (let i = 0; i < last; i++) { 341 | spool.push({ 342 | v: () => 343 | fetchHtml(messagesURL(i)).then(html_ => { 344 | const messages = Array.from(html_?.querySelectorAll('#list_members_my_conversations_items .item > a') || []).map(a => a.href); 345 | messages.forEach((m, j) => spool.push({ v: () => checkMessageHistory(m), p: 1 })); 346 | }), p: 2 347 | }); 348 | } 349 | spool.run(); 350 | 351 | let c = 0; 352 | function checkMessageHistory(url) { 353 | fetchHtml(url).then(html => { 354 | const hasFriendRequest = html.querySelector('input[value=confirm_add_to_friends]'); 355 | const hasOriginalText = html.querySelector('.original-text')?.innerText; 356 | const id = url.match(/\d+/)[0]; 357 | if (!(hasOriginalText || hasFriendRequest)) { 358 | const deleteURL = `${url}?mode=async&format=json&function=get_block&block_id=list_messages_my_conversation_messages&action=delete_conversation&conversation_user_id=${id}`; 359 | spool.push({ 360 | v: () => fetch(deleteURL).then(r => { 361 | console.log(r.status === 200 ? ++c : '', r.status, 'delete', id, 362 | html.querySelector('.list-messages').innerText.replace(/\n|\t/g, ' ').replace(/\ {2,}/g, ' ').trim()); 363 | }), p: 0 364 | }); 365 | } else { 366 | console.log(hasOriginalText, url); 367 | if (hasFriendRequest) { 368 | spool.push({ v: () => acceptFriendRequest(id), p: 0 }); 369 | } 370 | } 371 | }); 372 | } 373 | } 374 | 375 | //==================================================================================================== 376 | 377 | function route() { 378 | if (RULES.IS_LOGGED_IN) { 379 | setTimeout(processFriendship, FRIEND_REQUEST_INTERVAL); 380 | if (RULES.IS_MEMBER_PAGE || RULES.IS_COMMUNITY_LIST) { 381 | createFriendButton(); 382 | } 383 | if (RULES.HAS_VIDEOS) { 384 | defaultSchemeWithPrivateFilter.privateFilter.push( 385 | { type: 'button', innerText: 'check access 🔓', callback: requestAccess }); 386 | } 387 | } 388 | 389 | if (RULES.paginationElement && !RULES.IS_MEMBER_PAGE && !RULES.IS_MINE_MEMBER_PAGE) { 390 | createInfiniteScroller(store, handleLoadedHTML, RULES); 391 | shouldReload(); 392 | } 393 | 394 | if (RULES.HAS_VIDEOS) { 395 | watchDomChangesWithThrottle( 396 | document.querySelector('.content'), 397 | () => { 398 | const containers = getAllUniqueParents(RULES.GET_THUMBS(document.body)); 399 | containers.forEach((c) => handleLoadedHTML(c, c)); 400 | createInfiniteScroller(store, handleLoadedHTML, RULES); 401 | }, 1000, 1); 402 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 403 | animate(); 404 | } 405 | 406 | if (RULES.IS_VIDEO_PAGE) { 407 | createDownloadButton(); 408 | createPrivateVideoFriendButton(); 409 | } 410 | 411 | if (RULES.IS_MESSAGES) { 412 | const button = parseDom(''); 413 | document.querySelector('.headline').append(button); 414 | button.addEventListener('click', clearMessages); 415 | } 416 | } 417 | 418 | //==================================================================================================== 419 | 420 | console.log(LOGO); 421 | 422 | const ANIMATION_DELAY = 500; 423 | const FRIEND_REQUEST_INTERVAL = 5000; 424 | 425 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 426 | const { state, stateLocale } = store; 427 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 428 | store.subscribe(applyFilters); 429 | 430 | route(); 431 | -------------------------------------------------------------------------------- /camwhores-imporved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name CamWhores.tv Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 2.2.3 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, private/public, include/exclude phrases. Mass friend request button. Download button 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.camwhores.tv/* 10 | // @exclude *.camwhores.tv/*mode=async* 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/lskdb@1.0.2/dist/lskdb.umd.js 15 | // @run-at document-idle 16 | // @icon https://www.google.com/s2/favicons?sz=64&domain=camwhores.tv 17 | // ==/UserScript== 18 | /* globals $ */ 19 | 20 | const { Tick, parseDom, fetchHtml, AsyncPool, wait, computeAsyncOneAtTime, timeToSeconds, 21 | circularShift, range, watchDomChangesWithThrottle, objectToFormData, parseDataParams, sanitizeStr, 22 | getAllUniqueParents, downloader, DataManager, createInfiniteScroller } = window.bhutils; 23 | const { JabroniOutfitStore, defaultStateWithDurationAndPrivacy, JabroniOutfitUI, defaultSchemeWithPrivateFilter } = window.jabronioutfit; 24 | const { LSKDB } = window.lskdb; 25 | 26 | const LOGO = `camwhores admin should burn in hell 27 | ⣿⢏⡩⡙⣭⢫⡍⣉⢉⡉⢍⠩⡭⢭⠭⡭⢩⢟⣿⣿⣻⢿⣿⣿⣿⣿⡿⣏⣉⢉⣿⣿⣻⢿⣿⣿⠛⣍⢯⢋⠹⣛⢯⡅⡎⢱⣠⢈⡿⣽⣻⠽⡇⢘⡿⣯⢻⣝⡣⣍⠸⣏⡿⣭⢋⣽⣻⡏⢬⢹ 28 | ⣿⠦⡑⢜⡦⣳⡒⢄⠢⠌⢂⠜⣱⢋⡜⡡⢏⣾⢷⣻⢯⡿⣞⣿⣽⣻⢿⣹⢷⡂⣿⣾⡽⣻⣿⣿⢻⣌⣬⢩⢲⡑⡎⠼⡰⣏⣜⢦⡹⣷⣏⡟⡇⢨⡿⣝⡷⣎⠷⡌⢻⣜⡽⣯⣟⣷⣻⢷⣮⣹ 29 | ⣿⢧⢉⢲⢣⢇⡯⢀⠒⢨⠐⢌⠰⣋⠞⡱⢫⡞⢯⡍⠧⡙⠞⣬⠳⣝⠪⡙⡷⣏⣿⢾⡽⣿⣿⣻⡟⡾⣥⢺⢈⣷⣽⣶⣭⣷⣾⣾⢿⣼⣳⡻⡇⠰⣿⣝⢾⡹⠞⡤⣏⣾⣳⣟⣾⣳⣯⡿⣵⢳ 30 | ⡿⣇⠎⡜⣧⢺⣜⠠⡉⢄⠊⡄⠛⡌⢩⠳⣝⡎⠡⠐⠠⠉⠳⠄⠃⠄⢂⠱⡌⢃⣾⡿⣝⣿⣽⣿⣼⢳⡍⢞⣼⣿⣿⣿⣿⣿⣿⣿⣿⡞⣷⣛⡇⢘⡷⣯⢾⣹⢫⠔⣿⣞⡷⣿⣾⠿⣿⣿⡧⢿ 31 | ⣿⣹⠒⡌⠤⠣⣄⠣⡐⢌⠰⣈⠱⢈⠆⡻⠜⡠⠁⢊⠄⠀⠄⢀⠂⠜⡬⢛⠥⢂⣿⡿⣽⣿⣯⣿⢧⣏⢾⡙⢻⡿⠓⡌⡛⠿⣿⣿⣿⣻⡵⣯⠇⢨⣟⣞⣳⡝⣎⢢⡽⣟⣳⣼⣧⣾⣼⣷⣿⣿ 32 | ⣷⣏⠲⡈⢆⢳⣤⠃⡜⣀⠣⡐⢌⠢⢌⠑⡈⠐⡀⠠⠀⠌⠀⢂⠈⢌⡱⢈⠎⢀⣿⣿⣳⣿⡆⢻⡷⣞⡞⡬⢩⡐⣛⠶⣍⡲⣭⣿⣿⣿⡽⣞⡇⢨⣟⣾⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 33 | ⣷⣟⠧⡘⢠⠎⠔⠣⡐⢄⠢⡑⠌⠢⠌⡐⠤⢁⠐⠠⢁⠂⡁⠂⢌⡰⢆⡍⢂⠠⣿⡷⢿⠿⣃⢻⡛⡭⣝⢰⠣⡜⣈⠣⣜⣻⣿⣿⣿⣿⣟⣧⡇⠠⡿⣽⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 34 | ⣿⣯⣗⢡⠂⠆⠀⠠⠑⡌⢢⠁⠌⡐⢢⢁⠒⡈⢌⠢⢅⢎⡰⢉⢢⡙⢦⡘⢄⠠⢏⡱⣋⢾⣩⠷⣙⠶⡡⢎⢷⡘⢧⣟⡾⣱⠿⣿⣿⣿⢿⣳⡇⢘⡷⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 35 | ⡧⠩⠜⣠⢹⠆⠀⠀⠱⠈⠄⡈⠐⡀⠆⡈⠆⡁⢆⡘⠌⢎⡱⢋⡖⣩⠒⠥⢊⠴⣋⢶⡹⣎⡗⣯⠝⣎⡗⢎⠾⣹⢟⣮⢳⣭⢻⡹⣿⣿⡿⣯⡇⠘⣧⣽⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 36 | ⣟⣷⣻⢞⣻⡃⠀⠀⢡⠉⡐⠄⣃⠐⡄⠐⠠⢁⠂⠜⡈⠦⣑⢫⠴⡡⢉⠆⣭⢚⡭⢶⡹⢮⡝⣮⣛⢶⡹⣎⡰⣹⢎⡷⣫⢞⢧⡻⣜⢿⣿⣳⡇⢸⡿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 37 | ⣿⣾⣽⣯⡷⣿⣹⠇⡢⠑⡌⠰⣀⠊⠄⠃⡌⠠⢈⠐⡀⢂⠉⢆⡓⠰⡁⢞⡴⣋⡞⣧⣛⢧⣛⢶⡹⣎⠷⣝⢲⣭⣛⢶⡹⣎⢷⡹⣎⠿⣜⢷⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿ 38 | ⣿⡿⣿⣷⣿⢷⡛⡜⢠⠓⣌⠱⣀⠣⢌⡐⠠⢁⠂⠂⠄⠡⢈⠂⠬⡑⢬⠳⡜⣥⣛⢶⡹⢮⡝⣮⢳⡭⣟⢮⣳⢮⡝⣮⣗⡻⣎⢷⣭⢻⡜⣯⢇⠼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⣿⣷⣿⣽⣻⢿ 39 | ⣿⣿⣿⣿⣻⡿⣗⠨⣅⠊⢤⠓⡄⡃⢆⡘⠰⡀⠌⠂⠌⡐⠠⠈⠄⡉⠊⠽⣸⢲⡝⣮⢽⣣⢟⡼⣣⢟⣼⣣⡟⣮⡽⢖⡻⡝⢮⢳⡎⣷⡹⣎⣿⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 40 | ⣿⣿⣿⣿⣿⣿⣇⠣⡜⢌⢂⠱⡈⠴⠡⢌⠱⡈⠤⢁⠒⠠⠁⠌⡐⢀⠁⢂⠄⠛⡼⣭⢷⡹⢮⣳⣛⣮⢷⣣⢟⠲⣙⠮⣵⢫⣛⡶⣽⣶⣿⣽⣾⢿⡿⣿⣾⣟⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 41 | ⣿⣿⣿⣿⣿⣿⢇⡓⠼⡌⢆⠣⢄⠃⡜⢀⠣⡘⢄⠣⡐⠠⢁⠐⠠⠀⠌⠠⢈⠐⣤⠙⠞⠽⢫⠗⠛⡼⣣⠟⡄⢣⢈⣿⣼⣿⣿⡹⠿⡽⢾⣿⣭⣿⢿⣧⡽⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣻⣽ 42 | ⣿⣟⣯⣿⢾⣿⡧⢘⠤⣙⠢⡍⢢⠱⡐⠢⠄⡑⠌⢢⢁⠣⠄⠂⠄⢁⠂⡁⠂⡔⠠⢉⠜⡀⠣⢌⡱⠠⢅⠊⡜⢠⠃⠬⠉⢃⢉⡙⡒⠰⡡⠾⣿⣿⣿⣾⣻⣷⣿⣿⣿⣿⣽⣻⣿⢿⣽⣿⣿⢿ 43 | ⣿⣾⣿⣾⣿⣿⡻⣌⢆⠡⢣⠙⣆⠡⣉⠳⣌⡔⢨⠀⠎⡰⢉⠜⡐⢢⠐⡠⢁⠐⡌⢂⠐⠠⠑⠢⠱⡉⢎⡱⢈⠣⢞⠤⡈⢄⠢⠖⡙⢢⠑⠢⢍⢿⣿⣿⣿⣷⡿⣷⣿⣻⣿⣿⣿⣮⡿⣿⣿⣿ 44 | ⣿⣿⣿⣿⣿⣿⣿⣽⣎⠧⡑⢊⡄⠓⡄⢣⠠⡙⠢⣍⠒⡄⠣⡘⢌⠡⢚⠰⡈⠔⠠⠀⠌⠠⢁⡃⠱⢈⠆⠴⢁⡜⢨⡖⠩⢌⠒⠡⠌⡐⢈⠒⡌⢢⠙⣿⣿⣿⣿⣿⣟⣿⣾⣿⣿⣿⣿⣿⣿⣿ 45 | ⣿⣿⣿⣿⣿⣿⣿⡞⢿⣿⣥⡣⠌⡱⢈⠄⢣⠐⡡⢂⠍⡘⠱⢴⡈⢆⡡⢃⠍⡊⡕⡨⢄⢃⠢⡘⢠⢣⡘⢤⠣⡌⢧⡘⢥⣪⣼⣦⣶⣾⣿⣿⣶⠈⠂⠌⠻⣿⣿⣿⣿⣷⣿⣯⢿⣿⣿⣿⣿⣿ 46 | ⣿⣿⣿⣿⣿⣿⣿⣿⡳⣎⠿⣿⣧⡄⠣⡘⢄⠣⡐⢡⠊⡔⢡⠎⢉⡑⠲⣧⡚⡴⣠⡑⢎⡴⢣⡝⣣⢖⡹⣎⣳⣹⣶⣭⣾⣿⣿⣿⣿⣿⣿⣿⡿⣧⢄⠀⡀⠉⢿⣿⣿⣿⣿⣿⣿⣽⡿⣿⣿⣿ 47 | ⣿⣿⣿⣿⣿⣿⣿⣿⡷⡹⡞⣍⢿⣿⣳⠰⡈⠔⡈⢆⠱⣠⠃⠌⢂⡰⣱⣼⣿⢷⣧⣽⣺⣼⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣽⣿⣿⣿⣿⣿⣿⣷⣾⠤⠀⠁⠂⠹⣿⣿⣿⣿⣿⣿⣿⣟⡿⣿ 48 | ⣿⣯⣿⣿⡿⣾⣻⢽⡳⢧⡹⢞⢦⡹⢿⣷⣍⠲⡑⣎⠳⣄⣷⣾⣿⢿⣻⣟⣯⣿⣻⣿⣿⣿⣿⡿⣟⣿⣻⢿⣽⣻⣾⢷⣻⡽⣾⣿⣿⣯⣿⣿⣽⣿⣿⣿⣐⠀⠂⠀⠀⠿⣿⣿⣿⣿⣿⣿⣿⣿ 49 | ⣟⢮⡝⡷⣛⠵⣋⢾⣙⢧⡙⣎⢳⡹⡌⢿⡿⣧⣵⢫⣿⠿⣟⣿⣻⢯⣷⡿⣯⣷⣟⣷⡿⣯⣷⢿⣻⣾⣟⣿⣾⣿⣿⣿⢿⣽⣟⣿⣿⣟⣿⣿⣿⣾⣿⣿⣿⣆⠀⠀⠀⠀⠐⣿⣿⣿⣿⣿⣿⣿ 50 | ⣟⠮⣼⢱⡹⢎⣝⡲⡝⢮⡕⡎⣥⢓⡹⢆⡹⣿⣯⣟⣮⢷⣻⣞⣿⣻⢯⣿⡽⣷⡿⣯⣿⢿⣽⡿⣟⣯⣿⣿⣿⣿⣿⡿⣟⣿⣞⣯⣿⣿⣻⣿⣿⣷⡿⣿⣿⣿⣬⡀⠀⠀⠀⠀⠋⣻⣿⣿⣿⣿ 51 | ⣯⠳⣌⠧⡝⣎⠶⣱⢋⢧⣛⠼⣄⠫⡴⡩⢆⠻⣿⣿⠽⣾⠽⣞⣷⣻⣟⡷⣿⢯⣿⢷⣻⣯⢷⣿⣻⣟⡿⣽⢿⡿⣿⣽⣿⣻⢾⣽⣿⣿⣻⣿⣿⣿⣿⣽⣿⣿⣿⣷⡀⠀⠀⠀⣀⣉⣿⣿⣿⣿ 52 | ⣗⡫⣜⢣⡝⣬⠓⣥⢋⠶⣩⢞⡬⡓⡥⢓⠮⣅⠻⣿⣿⣽⣻⣽⣾⣻⢾⣻⣽⣻⣞⣯⢷⡯⣟⣾⢳⡯⢿⡽⣫⣽⡳⣏⡾⣽⣛⢾⡳⣟⢿⣻⢿⣿⣿⣿⡾⣿⣿⣷⣿⡔⣠⣿⣹⣯⣿⣿⣿⣿`; 53 | 54 | GM_addStyle(` 55 | .item.private .thumb, .item .thumb.private { opacity: 1 !important; } 56 | .haveNoAccess { background: linear-gradient(to bottom, #b50000 0%, #2c2c2c 100%) red !important; } 57 | .haveAccess { background: linear-gradient(to bottom, #4e9299 0%, #2c2c2c 100%) green !important; } 58 | .friend-button { background: radial-gradient(#5ccbf4, #e1ccb1) !important; } 59 | `); 60 | 61 | class CAMWHORES_RULES { 62 | delay = 350; 63 | 64 | IS_FAVOURITES = /\/my\/\w+\/videos/.test(location.pathname); 65 | IS_MEMBER_PAGE = /\/members\/\d+\/$/.test(location.pathname); 66 | IS_MINE_MEMBER_PAGE = /\/my\/$/.test(location.pathname); 67 | IS_MESSAGES = /^\/my\/messages\//.test(location.pathname); 68 | IS_MEMBER_VIDEOS = /\/members\/\d+\/(favourites\/)?videos/.test(location.pathname); 69 | IS_VIDEO_PAGE = /^\/videos\/\d+\//.test(location.pathname); 70 | IS_LOGGED_IN = document.cookie.includes('kt_member'); 71 | 72 | constructor() { 73 | Object.assign(this, this.URL_DATA()); 74 | Object.assign(this, this.CALC_CONTAINER()); 75 | this.HAS_VIDEOS = !!this.CONTAINER; 76 | 77 | if (this.IS_FAVOURITES || this.IS_MEMBER_VIDEOS) { 78 | this.INTERSECTION_OBSERVABLE = document.querySelector('.footer'); 79 | watchDomChangesWithThrottle(document.querySelector('.content'), () => { 80 | Object.assign(this, this.CALC_CONTAINER()); 81 | }, 10); 82 | } 83 | } 84 | 85 | CALC_CONTAINER = (document_ = document) => { 86 | const paginationEls = Array.from(document_.querySelectorAll('.pagination')); 87 | const paginationElement = 88 | paginationEls?.[this.IS_MEMBER_PAGE && paginationEls.length > 1 ? 1 : 0]; 89 | 90 | const paginationLast = Math.max( 91 | ...Array.from(paginationElement?.querySelectorAll('a[href][data-parameters]') || [], (v) => 92 | parseInt(v.getAttribute('data-parameters').match(/from\w*:(\d+)/)?.[1]), 93 | ), 1); 94 | 95 | const CONTAINER = 96 | paginationElement?.parentElement.querySelector('.list-videos>div>form') || 97 | paginationElement?.parentElement.querySelector('.list-videos>div') || 98 | document.querySelector('.list-videos>div'); 99 | 100 | return { paginationElement, paginationLast, CONTAINER }; 101 | }; 102 | 103 | IS_PRIVATE(thumb) { 104 | return thumb.classList.contains('private'); 105 | } 106 | 107 | GET_THUMBS(html) { 108 | return Array.from( 109 | html.querySelectorAll('.list-videos .item') || 110 | html.querySelectorAll('.item') || 111 | html.children, 112 | ); 113 | } 114 | 115 | THUMB_IMG_DATA(thumb) { 116 | const img = thumb.querySelector('img.thumb'); 117 | const imgSrc = img.getAttribute('data-original'); 118 | img.removeAttribute('data-original'); 119 | return { img, imgSrc }; 120 | } 121 | 122 | THUMB_URL(thumb) { 123 | return thumb.firstElementChild.href; 124 | } 125 | 126 | THUMB_DATA(thumb) { 127 | const title = sanitizeStr(thumb.querySelector('.title').innerText); 128 | const duration = timeToSeconds(thumb.querySelector('.duration')?.innerText); 129 | return { title, duration }; 130 | } 131 | 132 | URL_DATA(url_, document_) { 133 | const url = new URL((url_ || window.location).href); 134 | const paginationOffset = 135 | parseInt((document_ || document).querySelector('.page-current')?.innerText) || 1; 136 | 137 | const { paginationElement, paginationLast } = this.CALC_CONTAINER(document_ || document); 138 | const el = paginationElement?.querySelector('a[data-block-id][data-parameters]'); 139 | const dataParameters = el?.getAttribute('data-parameters') || ''; 140 | 141 | const attrs = { 142 | mode: 'async', 143 | function: 'get_block', 144 | block_id: el?.getAttribute('data-block-id'), 145 | ...parseDataParams(dataParameters), 146 | }; 147 | 148 | Object.keys(attrs).forEach((k) => url.searchParams.set(k, attrs[k])); 149 | 150 | const paginationUrlGenerator = (n) => { 151 | Object.keys(attrs).forEach((k) => k.includes('from') && url.searchParams.set(k, n)); 152 | url.searchParams.set('_', Date.now()); 153 | return url.href; 154 | }; 155 | 156 | return { paginationOffset, paginationUrlGenerator, paginationLast }; 157 | } 158 | } 159 | 160 | const RULES = new CAMWHORES_RULES(); 161 | 162 | //==================================================================================================== 163 | 164 | function rotateImg(src, count) { 165 | return src.replace(/(\d)(?=\.jpg$)/, (_, n) => `${circularShift(parseInt(n), count)}`); 166 | } 167 | 168 | function animate() { 169 | const tick = new Tick(ANIMATION_DELAY); 170 | $('img.thumb[data-cnt]').off() 171 | document.body.addEventListener('mouseover', (e) => { 172 | if (!e.target.tagName === 'IMG' || !e.target.classList.contains('thumb') || !e.target.getAttribute('src')) return; 173 | const origin = e.target.src; 174 | if (origin.includes('avatar')) return; 175 | const count = parseInt(e.target.getAttribute('data-cnt')) || 5; 176 | tick.start( 177 | () => { e.target.src = rotateImg(e.target.src, count); }, 178 | () => { e.target.src = origin; }); 179 | e.target.closest('.item').addEventListener('mouseleave', () => tick.stop(), { once: true }); 180 | }); 181 | } 182 | 183 | //==================================================================================================== 184 | 185 | const createDownloadButton = () => downloader({ 186 | append: '.tabs-menu > ul', 187 | button: '
  • download 📼
  • ' 188 | }) 189 | 190 | //==================================================================================================== 191 | 192 | // since script cannot be reloaded and scroll params need to be reset according to site options 193 | function shouldReload() { 194 | const sortContainer = document.querySelector('.sort'); 195 | if (!sortContainer) return; 196 | watchDomChangesWithThrottle(sortContainer, () => window.location.reload(), 1000); 197 | } 198 | 199 | //==================================================================================================== 200 | 201 | const DEFAULT_FRIEND_REQUEST_FORMDATA = objectToFormData({ 202 | message: "", 203 | action: "add_to_friends_complete", 204 | function: "get_block", 205 | block_id: "member_profile_view_view_profile", 206 | format: "json", 207 | mode: "async" 208 | }); 209 | 210 | const lskdb = new LSKDB(); 211 | const spool = new AsyncPool(); 212 | 213 | function friendRequest(id) { 214 | const url = Number.isInteger(id) ? `${window.location.origin}/members/${id}/` : id; 215 | return fetch(url, { body: DEFAULT_FRIEND_REQUEST_FORMDATA, method: "post" }); 216 | } 217 | 218 | function getMemberLinks(document) { 219 | return Array.from(document.querySelectorAll('.item > a')).map(l => l.href).filter(l => /\/members\/\d+\/$/.test(l)); 220 | } 221 | 222 | async function getMemberFriends(id) { 223 | const url = `${window.location.origin}/members/${id}/friends/`; 224 | const document_ = await fetchHtml(url); 225 | const { paginationOffset, paginationUrlGenerator, paginationLast } = RULES.URL_DATA(new URL(url), document_); 226 | const pages = paginationLast ? range(paginationLast, 1).map(u => paginationUrlGenerator(u)) : [url]; 227 | const friendlist = (await computeAsyncOneAtTime(pages.map(p => () => fetchHtml(p)))).flatMap(getMemberLinks).map(u => u.match(/\d+/)[0]); 228 | friendlist.forEach(m => lskdb.setKey(m)); 229 | await processFriendship(); 230 | } 231 | 232 | let processFriendshipStarted = false; 233 | async function processFriendship(batchSize = 30) { 234 | if (!lskdb.isLocked()) { 235 | const friendlist = lskdb.getKeys(batchSize); 236 | if (friendlist?.length < 1) return; 237 | if (!processFriendshipStarted) { 238 | processFriendshipStarted = true; 239 | console.log('processFriendshipStarted'); 240 | } 241 | lskdb.lock(true); 242 | const urls = friendlist.map(id => `${window.location.origin}/members/${id}/`); 243 | await computeAsyncOneAtTime(urls.map(url => async () => { 244 | await wait(FRIEND_REQUEST_INTERVAL); 245 | return friendRequest(url); 246 | })); 247 | lskdb.lock(false); 248 | await processFriendship(); 249 | } 250 | } 251 | 252 | function createPrivateVideoFriendButton() { 253 | if (!document.querySelector('.no-player')) return; 254 | const member = document.querySelector('.no-player a').href; 255 | const button = parseDom(''); 256 | document.querySelector('.no-player .message').append(button); 257 | button.addEventListener('click', () => friendRequest(member), { once: true }); 258 | } 259 | 260 | function createFriendButton() { 261 | const button = parseDom('Friend Everyone'); 262 | document.querySelector('.main-container-user > .headline').append(button); 263 | const memberid = window.location.pathname.match(/\d+/)[0]; 264 | button.addEventListener('click', () => { 265 | button.style.background = 'radial-gradient(#ff6114, #5babc4)'; 266 | button.innerText = 'processing requests'; 267 | getMemberFriends(memberid).then(() => { 268 | button.style.background = 'radial-gradient(blue, lightgreen)'; 269 | button.innerText = 'friend requests sent'; 270 | }); 271 | }, { once: true }); 272 | } 273 | 274 | //==================================================================================================== 275 | 276 | // clean: Object.keys(localStorage).forEach(k => k.includes('lsm') && localStorage.removeItem(k)); 277 | 278 | async function requestAccess() { 279 | checkPrivateVidsAccess(); 280 | setTimeout(processFriendship, FRIEND_REQUEST_INTERVAL); 281 | } 282 | 283 | async function checkPrivateVidsAccess() { 284 | const checkAccess = async (item) => { 285 | const videoURL = item.firstElementChild.href; 286 | const doc = await fetchHtml(videoURL); 287 | 288 | if (!doc.querySelector('.player')) return; 289 | 290 | const haveAccess = !doc.querySelector('.no-player'); 291 | 292 | if (!haveAccess) { 293 | const uid = doc.querySelector('.message a').href.match(/\d+/).at(-1); 294 | lskdb.setKey(uid); 295 | item.classList.add('haveNoAccess'); 296 | } else { 297 | item.classList.add('haveAccess'); 298 | } 299 | } 300 | 301 | const f = []; 302 | document.querySelectorAll('.item.private').forEach(item => { 303 | if (!item.classList.contains('haveNoAccess') && !item.classList.contains('haveAccess')) { 304 | f.push(() => checkAccess(item)); 305 | } 306 | }); 307 | computeAsyncOneAtTime(f); 308 | } 309 | 310 | //==================================================================================================== 311 | 312 | function getUserInfo(document) { 313 | const uploadedCount = parseInt(document.querySelector('#list_videos_uploaded_videos strong')?.innerText.match(/\d+/)[0]) || 0; 314 | const friendsCount = parseInt(document.querySelector('#list_members_friends .headline')?.innerText.match(/\d+/g).pop()) || 0; 315 | // const isFriend = /is in your friends list/gi.test(document.body.innerText); 316 | return { uploadedCount, friendsCount } 317 | } 318 | 319 | async function acceptFriendRequest(id) { 320 | const url = `https://www.camwhores.tv/my/messages/${id}/`; 321 | await fetch(url, { 322 | "headers": { 323 | "Accept": "*/*", 324 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 325 | }, 326 | "body": `action=confirm_add_to_friends&message_from_user_id=${id}&function=get_block&block_id=list_messages_my_conversation_messages&confirm=Confirm&format=json&mode=async`, 327 | "method": "POST", 328 | }); 329 | await fetchHtml(`https://www.camwhores.tv/members/${id}/`).then(doc => console.log('userInfo', getUserInfo(doc), url)); 330 | } 331 | 332 | function clearMessages() { 333 | const messagesURL = id => `https://www.camwhores.tv/my/messages/?mode=async&function=get_block&block_id=list_members_my_conversations&sort_by=added_date&from_my_conversations=${id}&_=${Date.now()}`; 334 | const last = parseInt(document.querySelector('.pagination-holder .last > a').href.match(/\d+/)?.[0]); 335 | if (!last) return; 336 | 337 | for (let i = 0; i < last; i++) { 338 | spool.push({ 339 | v: () => 340 | fetchHtml(messagesURL(i)).then(html_ => { 341 | const messages = Array.from(html_?.querySelectorAll('#list_members_my_conversations_items .item > a') || []).map(a => a.href); 342 | messages.forEach((m, j) => spool.push({ v: () => checkMessageHistory(m), p: 1 })); 343 | }), p: 2 344 | }); 345 | } 346 | spool.run(); 347 | 348 | let c = 0; 349 | function checkMessageHistory(url) { 350 | fetchHtml(url).then(html => { 351 | const hasFriendRequest = html.querySelector('input[value=confirm_add_to_friends]'); 352 | const hasOriginalText = html.querySelector('.original-text')?.innerText; 353 | const id = url.match(/\d+/)[0]; 354 | if (!(hasOriginalText || hasFriendRequest)) { 355 | const deleteURL = `${url}?mode=async&format=json&function=get_block&block_id=list_messages_my_conversation_messages&action=delete_conversation&conversation_user_id=${id}`; 356 | spool.push({ 357 | v: () => fetch(deleteURL).then(r => { 358 | console.log(r.status === 200 ? ++c : '', r.status, 'delete', id); 359 | }), p: 0 360 | }); 361 | } else { 362 | console.log(hasOriginalText, url); 363 | if (hasFriendRequest) { 364 | spool.push({ v: () => acceptFriendRequest(id), p: 0 }); 365 | } 366 | } 367 | }); 368 | } 369 | } 370 | 371 | //==================================================================================================== 372 | 373 | function route() { 374 | if (RULES.IS_LOGGED_IN) { 375 | setTimeout(processFriendship, 3000); 376 | if (RULES.IS_MEMBER_PAGE) { 377 | createFriendButton(); 378 | } 379 | if (RULES.HAS_VIDEOS) { 380 | defaultSchemeWithPrivateFilter.privateFilter.push( 381 | { type: 'button', innerText: 'check access 🔓', callback: requestAccess }); 382 | } 383 | } 384 | 385 | if (RULES.paginationElement && !RULES.IS_MEMBER_PAGE && !RULES.IS_MINE_MEMBER_PAGE) { 386 | createInfiniteScroller(store, handleLoadedHTML, RULES); 387 | shouldReload(); 388 | } 389 | 390 | if (RULES.HAS_VIDEOS) { 391 | watchDomChangesWithThrottle( 392 | document.querySelector('.content'), 393 | () => { 394 | const containers = getAllUniqueParents(RULES.GET_THUMBS(document.body)); 395 | containers.forEach((c) => handleLoadedHTML(c, c)); 396 | createInfiniteScroller(store, handleLoadedHTML, RULES); 397 | }, 1000, 1); 398 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 399 | animate(); 400 | } 401 | 402 | if (RULES.IS_VIDEO_PAGE) { 403 | createDownloadButton(); 404 | createPrivateVideoFriendButton(); 405 | } 406 | 407 | if (RULES.IS_MESSAGES) { 408 | const button = parseDom(''); 409 | document.querySelector('.headline').append(button); 410 | button.addEventListener('click', clearMessages); 411 | } 412 | } 413 | 414 | //==================================================================================================== 415 | 416 | console.log(LOGO); 417 | 418 | const ANIMATION_DELAY = 500; 419 | const FRIEND_REQUEST_INTERVAL = 5000; 420 | 421 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 422 | const { state, stateLocale } = store; 423 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 424 | store.subscribe(applyFilters); 425 | 426 | route(); 427 | -------------------------------------------------------------------------------- /ebalka-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ebalka improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.2.1 5 | // @license MIT 6 | // @description Infinite scroll. Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*ebalka.*.*/* 10 | // @match https://*.ebalk*.*/* 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @run-at document-idle 15 | // @icon https://www.google.com/s2/favicons?sz=64&domain=wwwa.ebalka.link 16 | // ==/UserScript== 17 | 18 | const { timeToSeconds, sanitizeStr, parseDom, DataManager, createInfiniteScroller, parseDataParams } = window.bhutils; 19 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 20 | 21 | const LOGO = ` 22 | ⣿⣿⣿⣿⣿⣿⢿⣻⣟⣿⣻⣽⣟⡿⣯⣟⣯⡿⣽⣻⣽⢽⢯⡿⡽⡯⣿⢽⣻⢽⡽⣽⣻⣿⣿⢿⣟⣿⣟⣿⣿⢿⣿⣿⣟⣿⣻⣿⣿⢿⣿⢿⡿⣿⢿⡿⣿⢿⣟⣿ 23 | ⣿⣿⣿⣿⡿⣾⡿⣿⣽⣯⡿⣷⣟⣟⣷⣻⢾⣽⢽⣳⢿⢽⢯⢿⢽⢯⢿⡽⣞⣟⣞⣷⣿⣿⣾⢿⢿⢿⡽⣟⣿⣟⣿⡷⣿⣽⣟⣿⣽⣿⣻⣟⡿⣯⣿⣻⣿⣻⣯⣿ 24 | ⣿⣿⣯⣿⣟⣿⣻⣯⣷⣿⣻⡷⣯⢷⣯⣯⢿⣺⢿⣽⣻⡽⣯⢿⢽⣻⣯⣿⢿⢾⡻⡫⡳⡱⡱⢱⠱⡱⡙⡜⡕⡯⡻⣻⣽⣾⣟⣷⡿⣯⣿⣽⣿⢿⣾⢿⣾⢿⣟⣯ 25 | ⣿⣿⣻⣽⣿⡯⣿⢾⡷⣯⡷⣟⣯⣿⣳⣿⣻⡽⣟⡗⡟⡟⡽⡻⣛⢯⢻⢪⢳⢱⠱⡑⠕⡌⢌⠢⡑⢌⠢⡑⢌⢊⠎⡎⢮⢪⠯⣷⢿⣻⣽⣾⣻⣯⡿⣯⣯⢿⣽⣟ 26 | ⣿⣿⢿⣿⣿⡽⣽⣯⢿⡽⣯⣿⢿⡽⡯⡳⣓⢝⠜⡜⢜⠸⡨⡊⢆⢣⠱⡱⡱⡡⡃⢎⠪⢐⠡⠊⠄⠅⠢⡈⡂⠅⡊⢌⢊⠎⡕⡕⡏⡿⣽⣾⢿⡷⣿⣻⢾⣟⣯⡿ 27 | ⣿⣿⣿⡿⣾⢯⣗⣿⣽⢿⣻⢝⡗⡝⡎⡎⢆⠣⡑⢌⠢⢑⠐⢌⠢⠡⠣⡑⡱⡐⢌⠢⠡⡁⡊⠌⡨⢈⠂⢂⠂⠡⠨⢐⠐⠅⢕⢱⢱⢹⢸⡫⣿⣻⣗⣿⣟⣯⣯⢿ 28 | ⣿⣿⣾⣿⡿⣽⣞⣿⣞⢟⡎⡗⡝⡜⢌⢊⠢⢑⠨⢐⠨⠠⠑⠄⠅⠕⡡⢑⢌⢌⠢⡑⠡⢂⠂⠅⡐⠠⠈⠄⡈⡈⢐⠐⠨⢈⠢⢑⠌⢎⢎⢎⢏⣷⣿⡷⣟⣷⣟⡿ 29 | ⣿⣷⣿⣷⡿⣷⡿⡳⡕⣇⢏⢎⢎⢌⠢⠡⢊⢐⠨⠐⡈⠄⠅⠅⠅⠅⡂⢅⠢⡂⢕⠨⠨⢐⠨⠐⠠⢈⠐⠀⠄⠠⠀⠂⢁⠐⢐⠠⡁⡣⢊⠎⡎⡎⣿⣟⣿⡷⣟⣿ 30 | ⣿⣿⣷⣿⣿⢿⡹⡱⡕⡕⡕⢕⠔⡂⢅⢑⠐⡀⡂⠡⠐⡈⠄⠡⡈⡂⠌⡐⡰⠨⢢⢡⢑⢐⠠⠁⠅⠠⠀⢁⠀⠂⠈⠀⠂⢈⠀⡂⠐⠄⢅⠣⡑⢕⢕⡻⣗⣿⣻⡷ 31 | ⣿⣿⣯⢏⢾⢳⢱⢱⢱⢱⢑⠕⡨⢐⢐⠐⡈⠄⠄⡁⢂⠐⢈⠐⡀⢂⠅⡂⡪⡈⡪⡂⢎⡐⡨⠀⠅⠐⠈⠀⠀⠐⠈⠀⠁⠠⠀⠄⠡⠑⡐⡑⡘⠔⢅⢝⢽⣽⣯⢿ 32 | ⣿⣷⡿⣵⢫⢪⢪⢪⠪⡢⡃⢅⠢⢁⠂⡂⠂⢂⠐⡀⠄⠂⡀⢂⠐⡐⢐⠌⡆⡕⡔⡕⡕⡌⡢⠡⠈⠄⠂⠀⠂⠀⠀⠀⠐⠀⠂⠈⠄⡁⡂⠢⠡⡑⢅⠪⡪⣿⢾⣟ 33 | ⣿⣗⢭⢺⢸⢘⢔⢕⠱⡨⢌⠢⡁⡂⠅⠠⢁⠐⠀⠄⠐⠀⠄⠐⢀⠂⡡⢱⢱⢱⣏⣟⡎⡎⡢⢁⠅⠐⠀⠠⠀⠀⠀⠀⠂⠀⡈⢀⠂⡐⠠⠡⡑⢄⢑⠌⡪⣺⣟⢏ 34 | ⣿⣯⢮⢣⠱⡑⡌⢆⠣⡊⡢⢁⠢⠠⠁⠅⠐⢈⠀⠂⠈⠀⠄⠁⠄⠂⠂⢕⠱⡹⡺⠾⡱⡑⢌⢐⠠⠁⡈⠀⠀⠀⠀⠀⢀⠀⠄⠠⠀⡂⠡⡁⡂⡂⡂⡑⢌⠮⢏⠢ 35 | ⣿⡻⡪⡢⠣⡑⢜⠨⡊⡢⢊⢐⠌⡐⠡⢈⠐⠀⠄⠂⠈⠀⠀⠂⠐⢈⠨⠠⢑⠡⠣⠩⠨⢂⠅⡢⠨⠀⠄⠐⠀⠀⠂⠀⠀⡀⠄⠂⡐⠠⡁⡂⠔⢐⢐⠨⠢⡹⡐⢅ 36 | ⣿⣞⢜⢌⠪⡘⢔⢑⠌⡂⡢⢂⠢⠨⢐⠠⠈⠄⠂⠀⠄⠈⠀⢈⠠⠀⢂⢁⠢⡑⡡⢑⢑⢔⠡⢂⠅⠅⠌⡀⠂⠠⠀⠐⠀⡀⠄⠂⠄⠅⡂⡐⠨⢐⢐⠨⡊⣎⠪⡢ 37 | ⣿⡧⡣⡢⠣⡡⡑⢔⠡⢊⢐⠐⠌⠌⠄⢂⠡⠐⠀⡁⠀⠠⠈⠀⡀⠐⡀⢂⢂⢂⠢⢡⢑⠢⡁⡂⡊⠌⠔⡠⢈⠀⠂⡁⠄⡀⢂⠡⢁⢂⠂⠄⠅⡂⡂⠪⡰⡱⢑⢌ 38 | ⣿⣯⢪⢸⠨⡢⢊⠢⡑⡐⡐⠡⠡⢁⠅⡂⠂⠌⠠⠀⠌⠀⠄⠂⡀⢂⢐⢐⢐⠄⢅⠅⡕⡑⢔⠐⢌⠌⡊⡐⡐⠨⠐⡀⢂⢐⠐⡈⡐⡀⡊⠨⢐⠐⢌⢊⢆⢇⢕⠢ 39 | ⣿⣿⢕⢕⢱⢘⠔⡡⢂⠢⠊⠌⢌⢐⠐⠄⠅⠅⡡⢈⠐⠐⠐⡀⡂⠔⡐⡐⠅⢌⠢⡑⡸⣈⠢⡡⡑⡌⠔⡐⠨⠠⢁⠐⡀⢂⠂⡂⡐⡀⡂⠅⡂⡑⢔⢑⢜⢌⠢⣑ 40 | ⣿⣿⣗⡕⡕⢜⢌⠢⡑⠌⢌⢊⢐⢐⠨⠨⡈⠢⢐⢐⠨⠨⠐⠄⡂⠅⢆⠪⡸⡐⡑⢌⢌⠆⢕⢰⢨⠢⢑⠨⢈⠐⡀⢂⠐⡀⢂⢐⢀⠂⠄⠅⡂⡪⡐⡅⡇⣕⣼⡺ 41 | ⣿⣯⡿⣮⢪⢪⢂⢇⠪⡈⡂⡂⡂⠢⠨⢐⠈⢌⢐⢐⠨⠨⠨⠨⠠⡑⡐⢅⢂⠣⡪⡢⡱⡸⡨⡢⡣⢊⢐⠈⠄⢂⠐⡀⢂⠐⡀⡂⡐⠨⠨⠨⡂⡢⢪⠸⣜⢮⢖⣝ 42 | ⣿⣷⢿⣟⣧⡣⡣⡢⡃⡪⢐⢐⠌⠌⡨⢐⠨⢐⠠⠂⠌⠄⠅⡡⠡⠐⡐⡐⡐⢅⠪⣎⢮⢮⣫⢿⡘⢔⠠⡁⢊⢀⠂⡐⠠⠨⢀⢂⠂⠅⢕⠡⢒⠌⡆⡯⢮⡳⡝⡮ 43 | ⣿⡿⣯⣿⣽⣷⡕⡕⡌⡪⡐⠅⢌⢂⢂⠢⠨⢐⠠⠡⢁⢊⠐⡀⡂⠅⡐⡐⠄⡑⢜⡺⡝⣗⡏⠎⡎⡆⢅⠂⢅⠐⡐⠠⢁⠊⠄⡂⠅⠕⡁⣊⠢⡣⣱⢽⡳⡽⡺⣝ 44 | ⣿⣿⣯⣷⡿⣾⢿⣮⢪⢢⠱⡑⢅⠢⡂⠅⢅⠢⠨⢈⢐⢀⢂⢂⢐⢐⠠⠂⠅⡊⡢⡫⣪⠯⡊⢌⠪⡪⡂⢕⠠⡁⡂⠅⡂⠅⠅⡢⠡⡑⢌⠢⡣⡱⣕⢗⣝⢮⡫⡮ 45 | ⣿⣷⢿⣷⢿⣿⣻⣯⡷⣇⢇⡣⢅⠕⢌⢌⠢⠨⠨⡐⡐⡐⡐⠄⡂⠔⡈⠌⢌⠢⡣⣣⢏⠪⡐⠅⡅⡕⡕⡅⢆⠢⢂⠅⡢⠡⡑⡐⢅⠪⡘⡜⡜⡮⣎⢗⣕⢧⢳⡹ 46 | ⣿⣟⣿⣟⣿⣽⢿⣾⣻⡿⡑⡕⣕⢱⠡⡢⢑⠅⢕⢐⢐⠔⡐⡁⡂⠅⡂⢅⢅⢕⢽⢱⠡⡃⡪⡨⢢⢱⡹⡸⡐⡅⠕⢌⠢⡑⡰⠨⡂⢕⢱⡸⡺⡜⡮⣳⡱⣝⢵⡹ 47 | ⣿⣿⣽⣿⣽⢿⣻⣷⢿⡑⠔⢅⢕⢕⢕⢜⢐⠅⢕⢐⠔⡐⠔⡐⠌⢌⠢⢑⠔⡕⡕⢕⢑⢌⠢⡊⢆⠣⡣⡫⡪⡪⡊⢆⢕⠰⡨⠢⡑⡕⡵⣹⢪⡳⣝⢮⢺⢜⢮⡪ 48 | ⣿⣟⣷⢿⣾⢿⣿⣽⡏⡎⠜⢌⢢⢱⢱⢱⡑⡕⢅⠢⡊⡐⠅⡌⢌⠢⡊⡢⢣⢣⠪⡊⢆⢅⠣⡊⢌⠪⡘⢜⢜⠜⡜⢔⢅⠕⡜⡸⡸⡸⡺⣜⣕⢯⢺⡪⣳⢹⡪⡺ 49 | ⣿⣿⣽⣿⣽⡿⣿⢾⠣⡊⡪⡊⢎⢌⢢⢑⢕⢕⢕⢱⢨⠨⡊⢔⢡⠱⡨⡪⡪⡪⡪⡘⢔⢡⢑⢌⢢⢣⡪⡘⡜⡜⡸⡐⡅⡣⡱⡡⡳⣙⢞⢼⡸⣕⢗⢽⡸⡵⣹⢹ 50 | ⣿⣾⣷⡿⣾⡿⣿⡏⡎⡪⡘⢌⠢⡢⡑⡕⠔⢕⢕⢕⢜⢌⢎⢢⠱⡑⡕⢜⢜⢜⡢⡣⡣⡪⡂⣎⢍⢗⢷⡐⡌⢎⢆⠕⢜⢌⢎⢎⢎⢎⢗⢧⡫⣎⢗⡵⣝⢞⢼⡱ 51 | ⣿⣿⣷⣿⡿⣿⡿⣗⡑⡌⡊⡆⢕⠌⢆⠪⡊⡆⢕⢕⢕⢕⢕⢱⢱⢑⠕⡕⢕⢕⢬⡣⢎⠎⡎⡆⡇⣯⣻⣿⣮⡆⡧⡹⢰⠱⡸⡘⡌⢎⢪⢣⢯⢎⣗⣝⡎⣗⢧⡫ 52 | ⣿⣿⣷⣿⢿⣿⣿⢣⠪⡪⢪⠸⡰⡑⡕⡱⡑⡌⢆⢕⢧⢣⢣⢣⢣⢣⠣⡣⡣⣳⣵⢱⢱⣱⣱⢜⣵⣿⣽⣿⣿⣿⡜⡜⡸⡘⠔⢅⢊⢢⢑⢅⠗⣗⢵⢺⡺⣪⢞⢮ 53 | ⣿⡻⣷⣿⣿⣿⣽⡪⡊⡎⡪⡊⢆⢣⢊⠆⡕⢌⢆⢕⢧⢣⢣⠣⡣⠣⡣⡣⡣⣻⣿⣷⣿⣿⣿⣷⣿⣿⣯⣷⣿⣿⣿⣜⢔⢌⢕⠡⢊⠔⢌⢂⢇⠳⣝⣗⢯⡺⣕⣗ 54 | ⣿⣝⢞⢿⣻⣾⡿⣇⢣⢣⢑⢎⢪⠢⡣⡱⡸⡨⡢⡃⡗⡕⡕⢕⡑⡅⡣⡱⡸⣸⣿⣿⣿⣿⣾⣿⣻⣾⣿⣻⣿⣽⣿⣿⣎⠢⡢⢑⠔⡨⢂⠕⡰⢩⢚⡮⣳⢝⣞⢮ 55 | ⣿⣞⢽⢵⢝⡯⡽⣸⡪⡢⡣⡣⡱⡑⡜⢔⢱⠨⡢⡊⡞⡜⢜⠔⡜⣐⠱⡨⢪⠪⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⣿⣿⡿⣳⣳⢕⢌⠢⡊⡐⡐⡡⢊⠢⡃⢯⣳⣻⡺⡵ 56 | ⣿⣞⢽⣕⢯⣺⢝⣿⡎⡎⡎⢆⢇⢎⢪⢊⢆⢣⢒⠜⣎⢎⢎⠪⡢⡂⡣⢊⠆⡝⢼⣫⡯⡯⣟⣿⢷⣿⣯⣿⢟⣗⢯⢞⡾⣇⢇⢕⢐⠅⡂⡊⠔⡑⠜⡜⣺⣺⡺⡽ 57 | ⣿⣞⢗⣗⢯⣞⢽⡺⣝⡜⡜⡜⡜⡜⡜⡔⡕⢕⢱⣑⡧⡣⡊⢎⠔⢌⠢⡡⢱⠸⡸⣺⡽⣽⣳⢽⢽⣺⢝⣮⣳⢳⡫⣗⡿⡽⡪⡢⡑⢌⢂⠪⠨⡨⠪⡸⢸⢮⢯⢯`; 58 | 59 | class EBALKA_RULES { 60 | delay = 250; 61 | 62 | paginationElement = [...document.querySelectorAll('.pagination')].pop(); 63 | CONTAINER = [...document.querySelectorAll('.content__video')].pop(); 64 | HAS_VIDEOS = !!document.querySelector('.card_video'); 65 | 66 | constructor() { 67 | const dataparams = parseDataParams( 68 | document.querySelector('.pagination__item.last').getAttribute('data-parameters'), 69 | ); 70 | const lastfrom = 71 | dataparams[ 72 | Object.keys(dataparams) 73 | .filter((k) => k.includes('from')) 74 | ?.pop() 75 | ]; 76 | this.paginationLast = parseInt(lastfrom) || 1; 77 | Object.assign(this, this.URL_DATA()); 78 | } 79 | 80 | GET_THUMBS(html) { 81 | return html.querySelectorAll('.card_video'); 82 | } 83 | 84 | THUMB_IMG_DATA() { 85 | return {}; 86 | } 87 | 88 | THUMB_URL(thumb) { 89 | return thumb.querySelector('.root__link').href; 90 | } 91 | 92 | THUMB_DATA(thumb) { 93 | const title = sanitizeStr(thumb.querySelector('.card__title').innerText); 94 | const duration = timeToSeconds( 95 | [...thumb.querySelector('.card__spot').children].pop().innerText, 96 | ); 97 | return { title, duration }; 98 | } 99 | 100 | URL_DATA() { 101 | const url = new URL(window.location.href); 102 | const paginationOffset = 103 | parseInt( 104 | document.querySelector('.pagination__item_active,input.pagination__item').innerText, 105 | ) || 1; 106 | const el = document.querySelector('.pagination__item.next'); 107 | 108 | const attrs = { 109 | mode: 'async', 110 | function: 'get_block', 111 | block_id: el?.getAttribute('data-block-id'), 112 | ...bhutils.parseDataParams(el?.getAttribute('data-parameters')), 113 | }; 114 | 115 | Object.keys(attrs).forEach((k) => url.searchParams.set(k, attrs[k])); 116 | 117 | const paginationUrlGenerator = (n) => { 118 | Object.keys(attrs).forEach((k) => k.includes('from') && url.searchParams.set(k, n)); 119 | url.searchParams.set('_', Date.now()); 120 | return url.href; 121 | }; 122 | 123 | return { paginationOffset, paginationUrlGenerator }; 124 | } 125 | } 126 | 127 | const RULES = new EBALKA_RULES(); 128 | 129 | //==================================================================================================== 130 | 131 | function animateThumb(thumb) { 132 | const el = thumb.querySelector('.card__thumb_video'); 133 | const src = el.querySelector('.card__image').getAttribute('data-preview'); 134 | 135 | el.classList.add('video-on'); 136 | 137 | const videoElem = 138 | parseDom(``); 140 | el.appendChild(videoElem); 141 | 142 | return { 143 | removeElem: () => { 144 | el.classList.remove('video-on'); 145 | videoElem.remove(); 146 | }, 147 | }; 148 | } 149 | 150 | function animate() { 151 | function handleThumbHover(e) { 152 | if (e.target.tagName !== 'IMG') return; 153 | const thumb = e.target.parentElement.parentElement.parentElement; 154 | const { removeElem } = animateThumb(thumb); 155 | thumb.addEventListener('mouseleave', removeElem, { once: true }); 156 | } 157 | 158 | RULES.CONTAINER.addEventListener('mouseover', handleThumbHover); 159 | } 160 | 161 | //==================================================================================================== 162 | 163 | function route() { 164 | if (RULES.paginationElement) { 165 | createInfiniteScroller(store, handleLoadedHTML, RULES); 166 | } 167 | 168 | if (RULES.HAS_VIDEOS) { 169 | animate(); 170 | handleLoadedHTML(RULES.CONTAINER); 171 | new JabroniOutfitUI(store); 172 | } 173 | } 174 | 175 | //==================================================================================================== 176 | 177 | console.log(LOGO); 178 | 179 | const store = new JabroniOutfitStore(defaultStateWithDuration); 180 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 181 | store.subscribe(applyFilters); 182 | 183 | route(); 184 | -------------------------------------------------------------------------------- /erome-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Erome Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 3.0.4 5 | // @license MIT 6 | // @description Infinite scroll. Filter photo/video albums. Toggle photos in albums. Skips 18+ dialog 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match *://*.erome.com/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=erome.com 11 | // @run-at document-idle 12 | // @grant GM_addStyle 13 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 15 | // @downloadURL https://update.sleazyfork.org/scripts/492883/Erome%20Improved.user.js 16 | // @updateURL https://update.sleazyfork.org/scripts/492883/Erome%20Improved.meta.js 17 | // ==/UserScript== 18 | /* globals $ LazyLoad */ 19 | 20 | const { parseDom, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 21 | const { JabroniOutfitStore, defaultStateWithDurationAndPrivacy, JabroniOutfitUI, defaultSchemeWithPrivateFilter } = window.jabronioutfit; 22 | 23 | const LOGO = ` 24 | ⡝⣝⢝⢝⢝⢝⢝⢝⢍⠭⡩⢍⠭⡩⡍⡭⢍⠭⡍⡭⡙⡍⢏⢝⠩⡩⡋⡍⡏⡝⡝⡽⡹⡹⡹⡙⡝⡙⡝⢍⠭⡩⢍⠭⡩⡩⡩⡩⡩⡍⡭⡩⡍⣍⢫⡩⡹⡩⡹⡩ 25 | ⡱⡱⡕⡇⣏⢎⢮⣪⢪⡪⣪⢪⡪⣪⢪⡪⣪⡪⡪⡢⡣⢪⢢⡣⡑⢌⠆⡪⡪⡪⡚⡜⢜⢸⠨⡊⡎⢎⢮⢣⠯⢮⠳⡝⡼⡪⢮⢣⠳⡕⠵⡕⠧⡳⢕⠧⡫⠮⡳⡹ 26 | ⢕⢝⡜⡎⣎⢧⢓⢎⠇⠕⢅⠣⡩⡘⡌⡎⡆⢇⠕⡕⢅⢇⢷⢱⢘⢔⢅⢇⢕⢅⠇⡪⢨⢂⠣⡱⡘⢜⢌⢎⢎⠢⡃⠕⡌⢌⢢⠡⡱⢘⠌⡢⢃⢊⢢⢑⢌⢊⢆⠪ 27 | ⢳⢱⢕⢝⢜⢜⢎⢇⢇⠣⡡⢱⢐⢅⢇⠎⢜⠰⡑⡜⢜⠜⡜⡜⡌⡺⡵⡣⢣⢑⢌⠢⡑⡐⡑⠔⢅⠣⡊⡆⢇⡳⡘⢌⠌⢆⠆⡃⡊⡢⡑⢌⠢⡑⢅⠆⡕⢌⢢⠱ 28 | ⡱⡣⡳⡹⡸⡱⡕⣕⢅⠕⢌⢆⢇⠣⢅⠣⡑⢅⢊⠌⡢⡑⡕⡕⡧⡣⣏⠎⢜⠰⡐⡡⠂⢌⠢⢑⠅⢕⢑⠜⡌⡎⢎⢇⢕⢑⢌⠪⡐⡌⢌⠆⡣⢡⠱⡨⡂⡣⠪⡘ 29 | ⡝⣜⢕⢝⡜⣕⢕⢕⢢⢑⢕⠜⡌⢎⠪⡨⠨⢂⠢⠨⡐⢌⠆⡇⡯⣺⠸⡈⢎⢪⢐⢐⠡⠡⡈⠢⠡⡑⢌⢪⢘⢌⢎⢎⢎⢆⠥⡑⡌⡌⠆⢕⢌⠢⡑⢔⢌⢪⢘⢌ 30 | ⢪⢎⢮⢣⡣⡇⡗⡝⢔⢅⢇⢕⢑⢅⠕⠌⢌⠢⠨⢂⠪⡐⢕⢕⢝⢜⠅⡊⢜⢔⢕⢐⢅⢑⠨⡈⡊⢌⢢⢑⢅⢣⠪⡢⡣⡳⡹⡸⡲⣘⢌⠢⡂⡣⢊⠢⡱⢨⠢⡱ 31 | ⡣⡳⣱⢣⡣⡳⣱⢹⠸⡰⡑⡌⣊⠢⡡⠃⠅⠌⢌⢂⠕⢌⢪⢢⢣⡓⡨⠨⡸⡸⡐⢅⠢⡂⠕⡐⢌⠢⡑⢌⢆⢣⠱⡑⡕⡕⡵⡱⡱⡱⡕⣕⡑⢌⠢⡱⡘⠔⡅⢕ 32 | ⢎⢧⢳⡱⡕⣝⢜⢜⢜⢔⠱⡨⠢⡑⢌⠌⡊⢌⢂⠢⡡⡑⡱⢸⢸⡐⠌⠌⢜⠔⢅⠅⢕⠨⠨⡂⢅⢊⢌⠢⡱⡘⢜⢸⢸⢸⢸⡪⡪⡎⣎⢎⢮⢐⠡⢂⠪⡨⠢⡡ 33 | ⢫⡪⣣⢣⡫⡪⡎⡇⡇⢎⢪⠨⡊⡌⡢⢑⠨⡐⡐⡡⢂⠪⠨⡊⡆⡇⠌⠌⡆⡣⢡⠡⡑⢌⢊⠔⠡⡂⢆⠣⡪⢸⢘⠬⡒⡕⡵⡱⣣⢣⢇⢗⡕⢔⠡⡡⡑⢌⠪⡰ 34 | ⣕⢝⡜⣎⢞⢜⢎⢎⢎⠪⡢⢱⠨⡂⡪⢐⠡⢂⢊⠄⢕⠨⢊⠢⡑⢮⢨⢨⢢⢑⠅⡊⢔⠡⢂⠪⡨⢌⠢⡃⢎⢪⠸⡸⡸⡸⡸⣕⢕⢧⡫⡇⡇⢕⠨⣂⢪⢂⢇⢎ 35 | ⡎⡧⡳⡱⣕⢝⢎⢇⢇⢣⠪⡢⡃⡪⢂⠕⢌⠢⠡⡊⢔⠡⡡⡑⢕⢕⢵⡱⣑⢢⠱⠨⡂⢅⢅⠕⡐⢅⠕⡜⡸⡨⡪⡪⡪⡪⡳⣕⢝⡵⣝⢜⢬⢲⢹⢸⢜⣞⢜⢕ 36 | ⡮⣚⢎⢧⡣⣫⢺⢸⢸⠰⡱⡨⢢⢑⢅⠪⡂⢕⢑⠌⡢⢑⠰⡘⢔⢱⢱⡣⡪⢢⢃⢇⠪⠢⡑⢌⢊⢆⠣⡊⡆⢇⢎⢆⢇⢗⢽⢜⡵⡝⣜⢜⡜⡜⡜⡜⣝⢜⢜⢌ 37 | ⣞⢜⡕⣇⢏⢮⢺⢸⢸⢸⠰⡡⢣⢑⢌⢪⠨⡢⠢⡑⡌⡢⠣⡑⢕⢱⠱⡕⡕⢕⠱⡐⢕⢑⢅⠕⢅⠆⡇⡣⡱⡱⡱⡱⡱⣝⢕⢧⡳⡱⢕⢗⢕⢵⡱⡣⡣⣣⢣⢃ 38 | ⡪⣇⢯⢪⢎⡗⡵⡱⡑⡕⡕⢕⢅⢕⢜⠰⡑⡌⢎⢢⠱⡘⢜⠸⡘⡜⡜⡵⡱⡱⡱⡑⢕⠱⡨⡊⡪⡸⡨⡪⡪⡊⡎⣎⢞⣎⢗⡵⣳⢕⡝⡜⡕⡕⡜⡜⡵⣳⢣⡳ 39 | ⢽⡸⣪⢳⢕⢽⢜⢜⢌⢎⢎⢎⢆⢣⢊⢎⠜⡌⢆⢕⢱⠸⡘⡜⡜⡜⡜⡵⡣⡣⡪⡪⢪⠪⡢⢣⢱⢸⢘⢌⢎⢎⢎⢮⡳⡵⣝⡾⣵⢝⣮⣺⡸⡜⡜⣎⢞⡵⡫⣫ 40 | ⡞⡮⡮⡳⣝⢮⢳⠱⡑⢕⢱⢱⢱⢑⢅⢇⢕⠕⡕⡅⡇⡇⡇⡇⡇⡇⡗⣝⡎⡎⡎⡎⡎⡎⢎⢎⢪⢪⢪⢪⡪⣪⢳⢝⡽⣝⣗⢿⣕⡝⣞⣞⢾⢽⣺⢺⢽⡪⡣⡣ 41 | ⢏⢇⢏⢎⢎⠪⡂⢇⢃⢣⠱⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡜⡎⣞⢎⢮⢪⢪⢪⢪⢪⢪⢪⡪⡪⡣⣣⢣⢏⣗⢽⢜⣮⡳⣕⢝⢎⢮⢪⢳⢸⢪⡳⣝⣞⣮ 42 | ⠔⡅⢕⢅⢣⠱⡸⢨⢪⠢⡣⢢⢪⢪⢪⠪⡪⠸⡘⢜⢜⢜⡜⡎⡮⡪⡺⡸⡕⡇⡗⡕⣕⢵⢱⢣⡣⡇⡯⡺⡸⣪⡳⢕⢝⢜⢔⢕⢕⢣⢳⢱⢱⢱⢱⠱⣹⡪⡎⡎ 43 | ⡑⢌⠢⠢⡑⢌⢌⡢⡡⣃⠪⠪⡘⡜⢌⠪⡨⢊⠜⡨⡊⡧⡳⣝⢼⣪⡳⡭⣳⡹⡸⡜⡜⣜⢜⢕⡕⣝⢜⢎⢧⢳⡱⡱⡑⡕⢕⠕⡕⢕⢕⢕⢕⢕⢅⢇⢇⢯⢪⠨ 44 | ⡪⡢⠣⢣⢡⡑⡔⡑⢍⢆⠇⡏⢎⠌⠆⢕⠨⡐⠌⡢⢱⢹⢝⡾⣽⣺⢮⡫⣞⠎⡎⡪⢪⠢⡣⡳⣹⢜⡵⣹⣪⢳⡑⢅⠕⡜⡰⢑⠜⢌⢊⠢⡑⢅⠣⡑⢅⠣⡑⠕ 45 | ⢕⠬⡩⠪⡒⡜⡬⡪⡜⡔⡥⡣⡣⣑⢅⢅⢂⢂⠑⢌⠪⡪⡯⣞⣞⢮⡻⣾⢑⠕⢅⠣⡑⢕⢱⢱⢱⣻⣺⡵⣳⣇⢧⢣⠣⣊⢢⠱⡘⡔⢕⠱⡘⣌⢎⢌⠆⡇⡣⠣ 46 | ⣷⣵⣮⣧⣧⣷⣾⣾⣾⣯⣯⣯⣯⣷⣿⣾⣷⣷⣵⣶⣵⣵⣿⣾⣾⣷⣯⣿⣼⣼⣼⣼⣼⣼⣶⣷⣿⣽⣾⣿⣷⣷⣷⣯⣷⣾⣾⣾⣾⣾⣾⣾⣾⣮⣾⣶⣵⣵⣮⣷`; 47 | 48 | console.log(LOGO); 49 | 50 | GM_addStyle(` 51 | .inactive-gm { background: #a09f9d; } 52 | .active-gm { background: #eb6395 !important; } 53 | `); 54 | 55 | //================================================================================================= 56 | 57 | (function disableDisclaimer() { 58 | if (!$('#disclaimer').length) return; 59 | $.ajax({ type: 'POST', url: '/user/disclaimer', async: true }); 60 | $('#disclaimer').remove(); 61 | $('body').css('overflow', 'visible'); 62 | })(); 63 | 64 | //================================================================================================= 65 | 66 | class EromeRules { 67 | delay = 150; 68 | 69 | constructor() { 70 | this.url = new URL(window.location.href); 71 | this.paginationOffset = parseInt(this.url.searchParams.get('page')) || 1; 72 | this.paginationLast = parseInt($('.pagination li:last-child()').prev().text()) || 50; 73 | this.paginationElement = document.querySelector('.pagination'); 74 | this.CONTAINER = document.querySelector('#albums'); 75 | } 76 | 77 | paginationUrlGenerator = (offset) => { 78 | this.url.searchParams.set('page', offset); 79 | return this.url.href; 80 | }; 81 | 82 | IS_PRIVATE(thumb) { 83 | return !!thumb.querySelector('.album-videos'); 84 | } 85 | 86 | THUMB_URL(thumb) { 87 | return thumb.querySelector('a[href]').href; 88 | } 89 | 90 | GET_THUMBS(html) { 91 | return html.querySelectorAll('div[id^=album-]'); 92 | } 93 | 94 | THUMB_IMG_DATA() { 95 | return {}; 96 | } 97 | 98 | THUMB_DATA(thumb) { 99 | const title = sanitizeStr(thumb.querySelector('.album-title').innerText); 100 | const user = sanitizeStr(thumb.querySelector('.album-user')?.innerText); 101 | const duration = 0; 102 | return { title: title.concat(` user:${user}`), duration }; 103 | } 104 | } 105 | 106 | const Rules = new EromeRules(); 107 | 108 | //================================================================================================= 109 | 110 | const IS_ALBUM_PAGE = /^\/a\//.test(window.location.pathname); 111 | 112 | function togglePhotoElements() { 113 | $('.media-group > div:last-child:not(.video)').toggle(state.showPhotos); 114 | $('#togglePhotos').toggleClass('active-gm', state.showPhotos); 115 | $('#togglePhotos').text(!state.showPhotos ? 'show photos' : 'hide photos'); 116 | } 117 | 118 | function setupAlbumPage() { 119 | $('#user_name') 120 | .parent() 121 | .append(''); 122 | 123 | $('#togglePhotos').on('click', () => { 124 | state.showPhotos = !state.showPhotos; 125 | togglePhotoElements(); 126 | }); 127 | 128 | window.addEventListener('focus', togglePhotoElements); 129 | togglePhotoElements(); 130 | } 131 | 132 | //================================================================================================= 133 | 134 | function init() { 135 | if (IS_ALBUM_PAGE) { 136 | setupAlbumPage(); 137 | } else { 138 | handleLoadedHTML(Rules.CONTAINER); 139 | createInfiniteScroller(store, handleLoadedHTML, Rules).onScroll(() => { 140 | setTimeout(() => new LazyLoad(), 100); 141 | }); 142 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 143 | } 144 | } 145 | 146 | //================================================================================================= 147 | 148 | Object.assign(defaultStateWithDurationAndPrivacy, { 149 | EROME: { 150 | showPhotos: { value: false, persistent: true, watch: true }, 151 | }, 152 | }); 153 | 154 | delete defaultSchemeWithPrivateFilter.durationFilter; 155 | defaultSchemeWithPrivateFilter.privateFilter[0].label = 'photos'; 156 | defaultSchemeWithPrivateFilter.privateFilter[1].label = 'videos'; 157 | 158 | //================================================================================================= 159 | 160 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 161 | const { state, stateLocale } = store; 162 | const { applyFilters, handleLoadedHTML } = new DataManager(Rules, state); 163 | store.subscribe(applyFilters); 164 | 165 | init(); 166 | -------------------------------------------------------------------------------- /javhdporn.net-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name javhdporn.net Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.1.1 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.javhdporn.*/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=javhdporn.net 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @run-at document-idle 15 | // ==/UserScript== 16 | 17 | const { timeToSeconds, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 18 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 19 | 20 | const LOGO = ` 21 | ⢆⠕⡡⡑⢌⠢⡑⢌⠢⡑⠔⢌⠪⡐⢌⢊⠢⡑⢌⠢⡑⡡⡑⢌⡊⡆⣕⡱⣌⣦⣎⣦⣧⣎⡆⢕⠐⢔⠨⡨⢂⠅⢕⠨⡂⡅⡢⢡⠢⡑⢌⠢⡢⢱⠰⡨⢢⢑⠌⡆ 22 | ⠕⡨⢂⠪⡐⡑⢌⠢⡑⢌⢌⠢⡡⢊⠔⡐⢅⠪⡐⢌⢢⡲⣜⡵⣽⣽⣾⣿⣿⣿⣿⣿⣿⣿⣿⡎⡜⢔⢱⠨⡢⠱⡡⠱⠨⠢⠱⡑⠜⠌⢆⠣⡊⡢⢃⠣⡃⡣⢃⠣ 23 | ⢇⢪⠸⡨⢌⠜⢌⠜⠌⢆⢕⠱⡘⢔⢑⠜⡐⢕⠸⡸⡷⣿⣻⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣽⣿⡿⣄⢕⣐⢅⢆⡕⣌⢎⢬⢕⢕⢜⢜⢜⢆⢗⢜⡔⣇⢧⢳⢸⢪⡣ 24 | ⢱⢐⢕⢔⣑⢌⢆⡕⡕⡥⡢⡕⡬⡲⡰⡱⡪⡲⣱⣻⣯⣷⣿⣻⣽⣿⣿⢿⣿⣟⣯⣷⣿⣿⣻⣿⡝⣜⢜⢜⡜⣜⢜⢜⢕⢕⢕⢜⢜⢜⢎⢎⢇⢇⡇⡧⡳⡹⡜⡮ 25 | ⢸⢱⢕⢇⢇⢗⢕⢵⢱⢣⢳⢱⢕⢵⢹⢜⢎⢞⢼⣽⡾⣷⣻⣟⣞⣷⣿⣿⣿⣿⢿⣟⣿⣟⣿⣿⢸⢱⡣⡣⡣⡣⡣⡳⡱⡹⡌⢜⢜⢕⢕⢵⢹⢸⡸⡜⡜⡎⡮⡺ 26 | ⡕⡕⣕⢕⢇⢏⢎⢇⢗⢕⢇⢧⢳⢱⢱⡱⡱⡕⡵⣿⣽⢿⣽⡯⣿⣻⣿⣟⣷⣻⡿⣯⣿⢯⢷⣻⢇⢇⢧⢣⢣⢣⡣⡣⡣⡣⡣⢱⢱⢱⢱⢕⢕⢕⢕⢕⢝⢜⢎⢎ 27 | ⢜⢜⢜⢜⢎⢎⢇⢗⢕⢕⢇⢇⢧⢳⢱⢱⢱⢕⢽⢷⣟⣿⢾⣟⣿⢽⣿⣿⣽⣿⡿⡯⡯⣟⡵⣗⢕⢕⢝⡜⡜⡜⡜⡜⡜⡜⡬⢸⢸⢸⢸⢸⢸⢱⢱⢹⢸⢱⡱⡹ 28 | ⡇⡏⡎⡎⡎⡮⡪⣪⢪⢣⡣⡳⡱⡱⡱⡱⡕⣕⣽⢿⣯⡿⣿⡿⣾⣻⡿⣿⣳⣿⢯⣿⣽⣗⣿⣳⣵⢵⢹⢸⢸⢸⢸⢸⢸⢸⢂⠕⡕⡕⡕⡕⡕⡕⣕⢕⢇⢇⢇⢏ 29 | ⢪⢪⡪⡪⣪⢪⡪⡪⡪⡣⡺⡸⡸⡸⡪⡪⡪⡲⣽⣻⡷⣿⣿⣿⣿⣾⢿⣿⣵⣿⣽⣿⣿⣻⣿⣿⡮⡣⡣⣣⡣⡣⡣⡣⡣⡣⡃⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡣⡳ 30 | ⢸⢸⢸⢸⢸⢸⢸⢸⢱⡱⡕⡕⡝⡜⡜⡎⡮⡪⡺⣽⣟⣯⣿⡿⣟⡿⣿⣿⣻⡽⣟⢟⢞⡕⡗⣕⣝⣾⣽⣿⣿⡿⣷⣕⢕⢕⠕⡸⡸⡘⡬⡪⡪⡪⡪⡪⡪⡣⣓⢝ 31 | ⢸⢸⢸⢸⢸⢸⢜⡪⡪⡪⡪⡪⡪⣪⢪⡪⣪⣮⢿⣺⣯⣿⣷⣟⡞⡞⡮⡹⡕⡏⡮⡳⡱⣕⢽⣺⣾⣿⢿⣻⡿⣿⣿⣿⢜⢔⠕⡅⡇⢇⢇⢎⢎⢎⢎⢎⢎⢎⢎⢮ 32 | ⡘⡜⡜⡜⡜⡜⡆⡇⡇⡇⡇⡇⡇⡇⡧⣳⣿⣽⣿⣻⣿⣿⣿⣿⣿⣎⣗⢝⡜⣜⢵⢝⣜⣾⣿⣿⣿⣻⣽⣿⣿⣿⣿⣿⢱⢱⠡⡃⡎⡎⢎⢪⢸⢸⢘⢜⢜⢜⢜⢜ 33 | ⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⣺⡿⣷⢿⣿⣽⣿⣾⣻⣿⣿⣿⣿⣷⣧⣧⣳⣷⢟⣟⣽⣿⣳⣟⣾⣿⣿⣿⣿⣿⢸⢰⠱⡡⢣⢣⢣⢣⢣⠣⡣⡣⡣⡣⡣⡣ 34 | ⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡺⣟⣿⣻⣿⣿⣿⣿⣾⣻⣿⣿⣽⣿⣽⣿⣿⣽⣝⣞⢟⣗⢽⣻⣿⣿⣿⣿⣾⡇⡇⡕⢕⠅⡣⡣⡣⡕⡜⡜⡜⡜⢜⢜⢜⢜ 35 | ⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⣿⣯⣿⣻⣿⣷⣿⣿⣻⣿⡿⣿⣿⣿⣿⣾⣿⣿⣞⣗⢗⣯⣻⣿⣿⣿⣽⣿⡇⡕⡜⡌⡂⡇⡏⣣⢣⢪⠪⡢⡣⡣⡣⡣⡣ 36 | ⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢗⡿⣾⣯⣿⣿⣿⣿⣿⣷⡽⡿⡿⣿⣻⣻⣿⢿⣿⣟⡿⣿⢽⣾⣿⣿⣽⣿⣿⢕⠕⡜⢌⠢⡱⣱⡪⡊⡆⡇⢇⢣⢣⠣⡣⡣ 37 | ⠱⡱⡱⡱⡑⡕⡕⢕⢱⢑⢕⢕⢕⢕⢕⣿⣻⣾⣻⣿⣟⣟⣿⣿⣽⣿⡿⣿⣻⣿⣾⣿⡿⡿⣿⣿⣿⣿⣫⣿⣻⣽⣾⡇⡇⢎⢎⠢⡱⡸⡸⡨⢪⢸⠸⡸⡸⡸⡸⡸ 38 | ⢱⢱⢱⢸⢸⢸⢸⢸⢸⢸⢸⢰⢱⢑⢕⣯⣿⢾⣻⣿⣽⣿⣻⣿⣯⣷⣿⣿⣿⡷⡿⡿⣿⣿⣷⣻⣽⣷⣿⣿⣿⣿⣿⣇⢇⢣⠪⡐⡱⡸⣆⢇⠕⡅⢇⢇⢎⢪⢸⢸ 39 | ⡨⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⣺⣿⣽⢿⣿⣿⣿⣿⣿⣿⡿⣟⡿⣽⣺⣝⡯⡿⣽⣺⢯⢿⣻⣿⣿⣿⣿⣿⢿⣿⠰⡑⡕⡨⢌⢗⢕⢱⢑⠕⡕⡜⢜⢜⢜⢜ 40 | ⡸⡸⣘⠬⡪⡪⡪⢪⠪⡪⡪⢪⢪⢪⣿⢷⣟⣿⣿⣿⣿⣻⣿⣟⣞⣗⣯⢷⣳⣗⡿⣽⣳⣽⡽⣿⢽⣿⣿⣿⣽⣾⣿⡗⢕⢱⢑⢌⢢⠣⡪⢢⢃⢇⢕⢜⢜⢔⢕⢜ 41 | ⣊⢎⢆⢇⢇⢇⢎⢎⢎⢎⢎⢎⢎⣾⣟⣿⣽⢿⣿⣷⣿⣿⣿⢽⡾⣵⣭⣽⣼⣼⣼⣼⣴⣵⡾⡾⡫⢽⣷⣿⣿⣿⣿⡏⡪⡢⢣⢊⢢⠣⡣⢣⢱⢡⢣⢱⢡⢣⢱⢑ 42 | ⢆⢇⢇⢣⢣⢣⢣⢣⢣⢣⠣⡣⡱⣷⣟⣿⢾⣿⣿⣿⣿⣿⡳⡱⡑⠝⢯⡿⣿⣽⣟⣿⠯⡣⡃⢎⠜⢜⣿⣿⣿⣿⣿⢚⣎⢞⣧⡪⠢⡣⡃⡇⡕⡜⢔⠕⡅⡇⡣⡣ 43 | ⡇⡇⡣⡣⡣⡱⡸⡸⡨⡒⡕⡕⡕⣿⣽⣟⣿⣻⡿⡻⣯⣗⢇⢇⢊⢊⠢⡙⣿⣯⡿⢡⢑⠔⢌⠢⡑⢕⢻⢏⢛⠾⣿⠪⡒⡵⡨⡷⡕⡅⡇⢎⠆⡇⡣⡣⢣⢱⢑⢅ 44 | ⡜⢜⢜⢌⢎⠎⡎⡪⡪⢪⠪⡪⣺⡿⣷⣟⣯⢗⢕⠡⡹⡱⡑⢔⠡⢂⢑⢐⢹⡗⢕⢅⠢⢊⢐⠐⢌⠢⡹⡨⠂⢕⢜⢕⠱⡱⡹⣾⢮⣮⣪⡸⡘⡌⡎⡜⢜⢌⢎⢎ 45 | ⢪⢪⢢⢣⠣⡣⡣⡣⢣⢣⠣⣝⣿⣻⣽⡾⣿⢱⢂⠕⡨⠊⡌⡢⠨⢂⠂⡂⣹⢑⠕⢄⠕⡐⠄⡑⡰⢑⠡⢂⠅⡑⡜⡐⢕⢱⢹⡺⣟⣾⣽⣻⢷⣧⠪⡊⡎⡪⡢⡣ 46 | ⡣⡱⡑⡕⢕⢕⢜⢜⠜⡌⡎⡎⣾⣿⣽⣟⣯⣳⢜⠔⡌⢌⢂⡊⡪⡐⡐⡐⣾⢐⢕⠡⡂⡢⢡⢘⢐⢐⠨⡐⡨⡂⢧⢑⢕⢱⢱⢝⣿⣳⣟⣾⣻⢾⣿⡨⡪⡊⡆⡇ 47 | ⡕⡕⢕⠕⡕⡅⡇⡎⢎⢪⢊⢎⢿⣞⣷⣿⣗⡵⣣⢳⢘⢔⠡⡊⡢⡱⡰⣸⡗⡜⡼⡸⡐⣔⠪⡐⢅⠢⡑⠔⡌⡌⢆⢕⢜⢜⢜⢮⣷⣻⣞⢾⣺⣟⡾⣷⢱⠸⡘⡌ 48 | ⡱⡸⡸⡸⡘⡌⡎⡪⡪⡪⢪⢮⣿⢯⣿⢾⣿⡮⣇⠇⢧⡣⡓⣌⡒⡌⡮⡺⡪⡪⣇⢯⢪⢎⢎⢊⠎⡎⡢⡱⡱⡑⡌⡎⡎⡮⣺⣽⣞⡷⣽⢻⡺⡮⣟⣟⠜⡜⢜⢜ 49 | ⢸⢨⢪⢢⠣⡣⡣⢣⠣⡪⣺⢿⣽⣿⡽⡯⣞⡿⣾⣕⡕⣪⡪⣢⡱⡱⡱⣼⣮⣯⡪⣾⢵⣝⢎⡎⡎⡎⡪⡪⡪⡪⡪⣪⣣⣯⡷⣟⡾⣝⢮⣳⢽⢽⡽⡇⡣⡣⢣⢱ 50 | ⢜⢜⢔⢕⢕⢕⢜⢜⢜⠜⣜⣿⣽⡾⣟⣯⢷⢯⣟⣗⣿⢶⣝⣼⡸⠼⡸⡸⡱⡫⡹⡙⡝⡝⡟⡞⡽⡜⡮⡺⡪⡮⣯⡾⣾⢷⣿⢿⢽⢽⢵⣫⣯⢿⢋⢎⢜⢌⢎⢎ 51 | ⢸⢰⠱⡱⡸⡰⡱⡱⡸⡘⡬⣷⣟⣿⣻⣽⣻⡽⣷⣻⡾⣓⢢⠲⡨⡣⡱⡸⡨⡊⡆⢇⢣⢱⢑⢕⠱⡑⡕⢅⢇⢮⣷⢿⣻⣟⣯⢯⢯⢾⢽⡾⡙⡅⢇⢪⠢⡣⡱⡸ 52 | ⢕⠕⡕⢕⢜⢌⢆⢇⢎⢎⢪⢺⣯⣯⡿⣯⡷⣿⢽⡿⡨⡢⢣⠣⡣⡱⢱⢘⢌⢎⢜⢜⢸⠰⡑⡅⢇⠇⡎⡪⣪⣿⡽⣟⣿⣽⣳⢯⢿⣽⡛⡅⢇⢕⢱⢡⢣⢱⢸⢨ 53 | ⠀⢣⠣⡣⡱⡑⡅⡇⢎⢪⠸⡼⣟⣾⣻⣯⢿⢽⢯⡗⢜⢌⢆⢇⢣⠪⡊⡎⡢⢣⠪⡂⡇⡕⢕⠱⡑⢕⢅⢷⣻⣾⣻⢿⣽⡺⡺⣽⢿⠪⡂⢇⠕⡜⡰⡑⡜⢔⢕⠱ 54 | ⢂⠀⠣⢑⠡⢃⢑⢘⢘⠘⠌⣟⣯⣟⣾⢽⣯⢿⣽⣻⠨⢂⢃⢊⢂⢃⠣⢑⠡⢃⢑⠑⢌⢘⢈⠪⢘⠨⣪⣿⣽⣳⣿⣻⢾⣝⣝⣾⢃⠡⠁⢅⠡⢁⢊⠨⠨⠨⠈⡊ 55 | ⠐⡡⠀⠡⢐⠐⡐⡀⡂⠌⣸⡺⣳⣻⣞⣿⢽⣽⢾⣯⠈⡐⠠⠁⡂⠡⠨⠠⠁⠅⠌⠨⠠⢁⠂⠡⠁⠌⡉⡞⡾⡽⣞⣯⡿⣯⣷⣟⣆⠈⡈⠠⠐⠀⠄⠂⠀⠁⡂⠔ 56 | ⠂⠌⠠⠁⢐⠐⢀⠂⠠⠁⡳⣳⢯⢷⣻⡾⣟⣾⣻⣞⠀⠄⢈⠠⠀⢁⠀⡁⢈⠀⡁⢈⠠⠀⢈⠀⠡⠀⠠⢈⢂⢻⣯⢷⣻⣽⢾⣽⢾⡀⠠⠐⠀⠐⠀⠄⠁⠡⠀⢂ 57 | ⠄⠈⠌⢂⠀⢂⢂⠄⠅⡂⡾⡽⣝⣯⡿⡯⣿⣺⢷⣻⡄⠂⢀⠠⠈⠀⠠⠀⠂⠀⠄⠀⠄⠈⠀⠐⠀⠈⠀⠀⠂⢱⠍⢿⢽⢾⢻⢽⣯⢷⣄⡀⠄⠂⠁⠀⡁⠨⠀⠄ 58 | ⢁⠐⠊⠠⠀⠂⠢⠨⢂⢂⢯⡯⣗⣯⣟⢸⡯⡢⣟⣷⡃⠠⠀⠄⡀⠂⠄⢂⠐⡀⡂⢐⠀⡂⠌⡀⢂⠡⠈⠠⠐⡸⡧⣿⢽⢧⠱⡐⢝⣯⢿⡦⢀⠠⠀⡁⠀⢐⠀⠂ 59 | ⣖⠀⡊⠨⡂⠨⣨⡪⠦⠖⡫⣏⢯⢞⡦⡽⡪⡌⣗⣯⠡⠈⡀⢁⠠⠈⡀⠄⠠⠀⠠⠀⠄⠠⠀⠄⠠⠀⡈⠀⠄⢸⠅⣪⡯⣻⠬⡬⡢⡹⡽⡉⠀⠀⡀⠂⡁⢐⠀⡁ 60 | ⠳⠑⡂⡑⠉⡐⠄⡂⡂⡡⣺⡪⣯⣫⡇⢾⡜⠬⣞⡮⡇⠠⠐⡀⠄⢂⢀⠂⡂⢁⠂⠡⠐⢐⠐⢈⠠⠐⡀⡐⢌⢜⡵⣜⢧⡻⡜⢔⢜⢼⣝⢗⠈⡀⠄⡁⡐⢐⠄⢂ 61 | ⡠⠡⡐⠄⡁⠆⠕⢌⠔⠔⠜⡞⠷⢽⠽⡜⡯⡇⡿⢝⠇⡨⢐⠠⠨⠠⢐⠠⠂⡂⠌⠄⠅⡂⠌⡐⠄⠅⠄⠪⠆⠯⠺⠝⠝⠣⠃⡓⠚⢙⠊⢃⠁⡂⠅⡂⢢⢑⢕⠁`; 62 | 63 | class JAVHDPORN_RULES { 64 | delay = 350; 65 | 66 | paginationElement = document.querySelector('.pagination'); 67 | paginationLast = parseInt( 68 | Array.from(document.querySelectorAll('.pagination a') || [], (a) => 69 | a.href.match(/\d+/g)?.pop(), 70 | )?.pop() || 1, 71 | ); 72 | CONTAINER = this.GET_THUMBS(document.body)?.[0].parentElement; 73 | 74 | constructor() { 75 | Object.assign(this, this.URL_DATA()); 76 | } 77 | 78 | THUMB_URL(thumb) { 79 | return thumb.querySelector('a').href; 80 | } 81 | 82 | GET_THUMBS(html) { 83 | return Array.from(html.querySelectorAll('article.thumb-block')); 84 | } 85 | 86 | THUMB_IMG_DATA(thumb) { 87 | return {}; 88 | } 89 | 90 | THUMB_DATA(thumb) { 91 | const title = sanitizeStr(thumb.querySelector('header.entry-header')?.innerText); 92 | const duration = timeToSeconds(thumb.querySelector('.duration')?.innerText); 93 | return { title, duration }; 94 | } 95 | 96 | URL_DATA() { 97 | const url = new URL(location.href); 98 | const paginationOffset = parseInt(location.pathname.match(/\/page\/(\d+)/)?.pop() || 1); 99 | if (paginationOffset < 2) url.pathname = `${url.pathname}/page/${paginationOffset}/`; 100 | 101 | const paginationUrlGenerator = (n) => { 102 | return url.href.replace(/\/page\/\d+/, () => `/page/${n}`); 103 | }; 104 | 105 | return { paginationOffset, paginationUrlGenerator }; 106 | } 107 | } 108 | 109 | const RULES = new JAVHDPORN_RULES(); 110 | 111 | //==================================================================================================== 112 | 113 | function router() { 114 | if (RULES.CONTAINER) { 115 | handleLoadedHTML(RULES.CONTAINER); 116 | } 117 | 118 | if (RULES.paginationElement) { 119 | createInfiniteScroller(store, handleLoadedHTML, RULES); 120 | } 121 | 122 | new JabroniOutfitUI(store); 123 | } 124 | 125 | //==================================================================================================== 126 | 127 | console.log(LOGO); 128 | 129 | const store = new JabroniOutfitStore(defaultStateWithDuration); 130 | const { state, stateLocale } = store; 131 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 132 | store.subscribe(applyFilters); 133 | 134 | router(); 135 | -------------------------------------------------------------------------------- /missav-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name missav.com Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.1.2 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.missav.*/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=missav.com 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @run-at document-idle 15 | // @downloadURL https://update.sleazyfork.org/scripts/494001/PornHub%20Improved.user.js 16 | // @updateURL https://update.sleazyfork.org/scripts/494001/PornHub%20Improved.meta.js 17 | // ==/UserScript== 18 | 19 | const { timeToSeconds, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 20 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 21 | 22 | const LOGO = ` 23 | ⢆⠕⡡⡑⢌⠢⡑⢌⠢⡑⠔⢌⠪⡐⢌⢊⠢⡑⢌⠢⡑⡡⡑⢌⡊⡆⣕⡱⣌⣦⣎⣦⣧⣎⡆⢕⠐⢔⠨⡨⢂⠅⢕⠨⡂⡅⡢⢡⠢⡑⢌⠢⡢⢱⠰⡨⢢⢑⠌⡆ 24 | ⠕⡨⢂⠪⡐⡑⢌⠢⡑⢌⢌⠢⡡⢊⠔⡐⢅⠪⡐⢌⢢⡲⣜⡵⣽⣽⣾⣿⣿⣿⣿⣿⣿⣿⣿⡎⡜⢔⢱⠨⡢⠱⡡⠱⠨⠢⠱⡑⠜⠌⢆⠣⡊⡢⢃⠣⡃⡣⢃⠣ 25 | ⢇⢪⠸⡨⢌⠜⢌⠜⠌⢆⢕⠱⡘⢔⢑⠜⡐⢕⠸⡸⡷⣿⣻⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣽⣿⡿⣄⢕⣐⢅⢆⡕⣌⢎⢬⢕⢕⢜⢜⢜⢆⢗⢜⡔⣇⢧⢳⢸⢪⡣ 26 | ⢱⢐⢕⢔⣑⢌⢆⡕⡕⡥⡢⡕⡬⡲⡰⡱⡪⡲⣱⣻⣯⣷⣿⣻⣽⣿⣿⢿⣿⣟⣯⣷⣿⣿⣻⣿⡝⣜⢜⢜⡜⣜⢜⢜⢕⢕⢕⢜⢜⢜⢎⢎⢇⢇⡇⡧⡳⡹⡜⡮ 27 | ⢸⢱⢕⢇⢇⢗⢕⢵⢱⢣⢳⢱⢕⢵⢹⢜⢎⢞⢼⣽⡾⣷⣻⣟⣞⣷⣿⣿⣿⣿⢿⣟⣿⣟⣿⣿⢸⢱⡣⡣⡣⡣⡣⡳⡱⡹⡌⢜⢜⢕⢕⢵⢹⢸⡸⡜⡜⡎⡮⡺ 28 | ⡕⡕⣕⢕⢇⢏⢎⢇⢗⢕⢇⢧⢳⢱⢱⡱⡱⡕⡵⣿⣽⢿⣽⡯⣿⣻⣿⣟⣷⣻⡿⣯⣿⢯⢷⣻⢇⢇⢧⢣⢣⢣⡣⡣⡣⡣⡣⢱⢱⢱⢱⢕⢕⢕⢕⢕⢝⢜⢎⢎ 29 | ⢜⢜⢜⢜⢎⢎⢇⢗⢕⢕⢇⢇⢧⢳⢱⢱⢱⢕⢽⢷⣟⣿⢾⣟⣿⢽⣿⣿⣽⣿⡿⡯⡯⣟⡵⣗⢕⢕⢝⡜⡜⡜⡜⡜⡜⡜⡬⢸⢸⢸⢸⢸⢸⢱⢱⢹⢸⢱⡱⡹ 30 | ⡇⡏⡎⡎⡎⡮⡪⣪⢪⢣⡣⡳⡱⡱⡱⡱⡕⣕⣽⢿⣯⡿⣿⡿⣾⣻⡿⣿⣳⣿⢯⣿⣽⣗⣿⣳⣵⢵⢹⢸⢸⢸⢸⢸⢸⢸⢂⠕⡕⡕⡕⡕⡕⡕⣕⢕⢇⢇⢇⢏ 31 | ⢪⢪⡪⡪⣪⢪⡪⡪⡪⡣⡺⡸⡸⡸⡪⡪⡪⡲⣽⣻⡷⣿⣿⣿⣿⣾⢿⣿⣵⣿⣽⣿⣿⣻⣿⣿⡮⡣⡣⣣⡣⡣⡣⡣⡣⡣⡃⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡣⡳ 32 | ⢸⢸⢸⢸⢸⢸⢸⢸⢱⡱⡕⡕⡝⡜⡜⡎⡮⡪⡺⣽⣟⣯⣿⡿⣟⡿⣿⣿⣻⡽⣟⢟⢞⡕⡗⣕⣝⣾⣽⣿⣿⡿⣷⣕⢕⢕⠕⡸⡸⡘⡬⡪⡪⡪⡪⡪⡪⡣⣓⢝ 33 | ⢸⢸⢸⢸⢸⢸⢜⡪⡪⡪⡪⡪⡪⣪⢪⡪⣪⣮⢿⣺⣯⣿⣷⣟⡞⡞⡮⡹⡕⡏⡮⡳⡱⣕⢽⣺⣾⣿⢿⣻⡿⣿⣿⣿⢜⢔⠕⡅⡇⢇⢇⢎⢎⢎⢎⢎⢎⢎⢎⢮ 34 | ⡘⡜⡜⡜⡜⡜⡆⡇⡇⡇⡇⡇⡇⡇⡧⣳⣿⣽⣿⣻⣿⣿⣿⣿⣿⣎⣗⢝⡜⣜⢵⢝⣜⣾⣿⣿⣿⣻⣽⣿⣿⣿⣿⣿⢱⢱⠡⡃⡎⡎⢎⢪⢸⢸⢘⢜⢜⢜⢜⢜ 35 | ⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⣺⡿⣷⢿⣿⣽⣿⣾⣻⣿⣿⣿⣿⣷⣧⣧⣳⣷⢟⣟⣽⣿⣳⣟⣾⣿⣿⣿⣿⣿⢸⢰⠱⡡⢣⢣⢣⢣⢣⠣⡣⡣⡣⡣⡣⡣ 36 | ⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡱⡺⣟⣿⣻⣿⣿⣿⣿⣾⣻⣿⣿⣽⣿⣽⣿⣿⣽⣝⣞⢟⣗⢽⣻⣿⣿⣿⣿⣾⡇⡇⡕⢕⠅⡣⡣⡣⡕⡜⡜⡜⡜⢜⢜⢜⢜ 37 | ⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⢪⣿⣯⣿⣻⣿⣷⣿⣿⣻⣿⡿⣿⣿⣿⣿⣾⣿⣿⣞⣗⢗⣯⣻⣿⣿⣿⣽⣿⡇⡕⡜⡌⡂⡇⡏⣣⢣⢪⠪⡢⡣⡣⡣⡣⡣ 38 | ⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢇⢗⡿⣾⣯⣿⣿⣿⣿⣿⣷⡽⡿⡿⣿⣻⣻⣿⢿⣿⣟⡿⣿⢽⣾⣿⣿⣽⣿⣿⢕⠕⡜⢌⠢⡱⣱⡪⡊⡆⡇⢇⢣⢣⠣⡣⡣ 39 | ⠱⡱⡱⡱⡑⡕⡕⢕⢱⢑⢕⢕⢕⢕⢕⣿⣻⣾⣻⣿⣟⣟⣿⣿⣽⣿⡿⣿⣻⣿⣾⣿⡿⡿⣿⣿⣿⣿⣫⣿⣻⣽⣾⡇⡇⢎⢎⠢⡱⡸⡸⡨⢪⢸⠸⡸⡸⡸⡸⡸ 40 | ⢱⢱⢱⢸⢸⢸⢸⢸⢸⢸⢸⢰⢱⢑⢕⣯⣿⢾⣻⣿⣽⣿⣻⣿⣯⣷⣿⣿⣿⡷⡿⡿⣿⣿⣷⣻⣽⣷⣿⣿⣿⣿⣿⣇⢇⢣⠪⡐⡱⡸⣆⢇⠕⡅⢇⢇⢎⢪⢸⢸ 41 | ⡨⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⣺⣿⣽⢿⣿⣿⣿⣿⣿⣿⡿⣟⡿⣽⣺⣝⡯⡿⣽⣺⢯⢿⣻⣿⣿⣿⣿⣿⢿⣿⠰⡑⡕⡨⢌⢗⢕⢱⢑⠕⡕⡜⢜⢜⢜⢜ 42 | ⡸⡸⣘⠬⡪⡪⡪⢪⠪⡪⡪⢪⢪⢪⣿⢷⣟⣿⣿⣿⣿⣻⣿⣟⣞⣗⣯⢷⣳⣗⡿⣽⣳⣽⡽⣿⢽⣿⣿⣿⣽⣾⣿⡗⢕⢱⢑⢌⢢⠣⡪⢢⢃⢇⢕⢜⢜⢔⢕⢜ 43 | ⣊⢎⢆⢇⢇⢇⢎⢎⢎⢎⢎⢎⢎⣾⣟⣿⣽⢿⣿⣷⣿⣿⣿⢽⡾⣵⣭⣽⣼⣼⣼⣼⣴⣵⡾⡾⡫⢽⣷⣿⣿⣿⣿⡏⡪⡢⢣⢊⢢⠣⡣⢣⢱⢡⢣⢱⢡⢣⢱⢑ 44 | ⢆⢇⢇⢣⢣⢣⢣⢣⢣⢣⠣⡣⡱⣷⣟⣿⢾⣿⣿⣿⣿⣿⡳⡱⡑⠝⢯⡿⣿⣽⣟⣿⠯⡣⡃⢎⠜⢜⣿⣿⣿⣿⣿⢚⣎⢞⣧⡪⠢⡣⡃⡇⡕⡜⢔⠕⡅⡇⡣⡣ 45 | ⡇⡇⡣⡣⡣⡱⡸⡸⡨⡒⡕⡕⡕⣿⣽⣟⣿⣻⡿⡻⣯⣗⢇⢇⢊⢊⠢⡙⣿⣯⡿⢡⢑⠔⢌⠢⡑⢕⢻⢏⢛⠾⣿⠪⡒⡵⡨⡷⡕⡅⡇⢎⠆⡇⡣⡣⢣⢱⢑⢅ 46 | ⡜⢜⢜⢌⢎⠎⡎⡪⡪⢪⠪⡪⣺⡿⣷⣟⣯⢗⢕⠡⡹⡱⡑⢔⠡⢂⢑⢐⢹⡗⢕⢅⠢⢊⢐⠐⢌⠢⡹⡨⠂⢕⢜⢕⠱⡱⡹⣾⢮⣮⣪⡸⡘⡌⡎⡜⢜⢌⢎⢎ 47 | ⢪⢪⢢⢣⠣⡣⡣⡣⢣⢣⠣⣝⣿⣻⣽⡾⣿⢱⢂⠕⡨⠊⡌⡢⠨⢂⠂⡂⣹⢑⠕⢄⠕⡐⠄⡑⡰⢑⠡⢂⠅⡑⡜⡐⢕⢱⢹⡺⣟⣾⣽⣻⢷⣧⠪⡊⡎⡪⡢⡣ 48 | ⡣⡱⡑⡕⢕⢕⢜⢜⠜⡌⡎⡎⣾⣿⣽⣟⣯⣳⢜⠔⡌⢌⢂⡊⡪⡐⡐⡐⣾⢐⢕⠡⡂⡢⢡⢘⢐⢐⠨⡐⡨⡂⢧⢑⢕⢱⢱⢝⣿⣳⣟⣾⣻⢾⣿⡨⡪⡊⡆⡇ 49 | ⡕⡕⢕⠕⡕⡅⡇⡎⢎⢪⢊⢎⢿⣞⣷⣿⣗⡵⣣⢳⢘⢔⠡⡊⡢⡱⡰⣸⡗⡜⡼⡸⡐⣔⠪⡐⢅⠢⡑⠔⡌⡌⢆⢕⢜⢜⢜⢮⣷⣻⣞⢾⣺⣟⡾⣷⢱⠸⡘⡌ 50 | ⡱⡸⡸⡸⡘⡌⡎⡪⡪⡪⢪⢮⣿⢯⣿⢾⣿⡮⣇⠇⢧⡣⡓⣌⡒⡌⡮⡺⡪⡪⣇⢯⢪⢎⢎⢊⠎⡎⡢⡱⡱⡑⡌⡎⡎⡮⣺⣽⣞⡷⣽⢻⡺⡮⣟⣟⠜⡜⢜⢜ 51 | ⢸⢨⢪⢢⠣⡣⡣⢣⠣⡪⣺⢿⣽⣿⡽⡯⣞⡿⣾⣕⡕⣪⡪⣢⡱⡱⡱⣼⣮⣯⡪⣾⢵⣝⢎⡎⡎⡎⡪⡪⡪⡪⡪⣪⣣⣯⡷⣟⡾⣝⢮⣳⢽⢽⡽⡇⡣⡣⢣⢱ 52 | ⢜⢜⢔⢕⢕⢕⢜⢜⢜⠜⣜⣿⣽⡾⣟⣯⢷⢯⣟⣗⣿⢶⣝⣼⡸⠼⡸⡸⡱⡫⡹⡙⡝⡝⡟⡞⡽⡜⡮⡺⡪⡮⣯⡾⣾⢷⣿⢿⢽⢽⢵⣫⣯⢿⢋⢎⢜⢌⢎⢎ 53 | ⢸⢰⠱⡱⡸⡰⡱⡱⡸⡘⡬⣷⣟⣿⣻⣽⣻⡽⣷⣻⡾⣓⢢⠲⡨⡣⡱⡸⡨⡊⡆⢇⢣⢱⢑⢕⠱⡑⡕⢅⢇⢮⣷⢿⣻⣟⣯⢯⢯⢾⢽⡾⡙⡅⢇⢪⠢⡣⡱⡸ 54 | ⢕⠕⡕⢕⢜⢌⢆⢇⢎⢎⢪⢺⣯⣯⡿⣯⡷⣿⢽⡿⡨⡢⢣⠣⡣⡱⢱⢘⢌⢎⢜⢜⢸⠰⡑⡅⢇⠇⡎⡪⣪⣿⡽⣟⣿⣽⣳⢯⢿⣽⡛⡅⢇⢕⢱⢡⢣⢱⢸⢨ 55 | ⠀⢣⠣⡣⡱⡑⡅⡇⢎⢪⠸⡼⣟⣾⣻⣯⢿⢽⢯⡗⢜⢌⢆⢇⢣⠪⡊⡎⡢⢣⠪⡂⡇⡕⢕⠱⡑⢕⢅⢷⣻⣾⣻⢿⣽⡺⡺⣽⢿⠪⡂⢇⠕⡜⡰⡑⡜⢔⢕⠱ 56 | ⢂⠀⠣⢑⠡⢃⢑⢘⢘⠘⠌⣟⣯⣟⣾⢽⣯⢿⣽⣻⠨⢂⢃⢊⢂⢃⠣⢑⠡⢃⢑⠑⢌⢘⢈⠪⢘⠨⣪⣿⣽⣳⣿⣻⢾⣝⣝⣾⢃⠡⠁⢅⠡⢁⢊⠨⠨⠨⠈⡊ 57 | ⠐⡡⠀⠡⢐⠐⡐⡀⡂⠌⣸⡺⣳⣻⣞⣿⢽⣽⢾⣯⠈⡐⠠⠁⡂⠡⠨⠠⠁⠅⠌⠨⠠⢁⠂⠡⠁⠌⡉⡞⡾⡽⣞⣯⡿⣯⣷⣟⣆⠈⡈⠠⠐⠀⠄⠂⠀⠁⡂⠔ 58 | ⠂⠌⠠⠁⢐⠐⢀⠂⠠⠁⡳⣳⢯⢷⣻⡾⣟⣾⣻⣞⠀⠄⢈⠠⠀⢁⠀⡁⢈⠀⡁⢈⠠⠀⢈⠀⠡⠀⠠⢈⢂⢻⣯⢷⣻⣽⢾⣽⢾⡀⠠⠐⠀⠐⠀⠄⠁⠡⠀⢂ 59 | ⠄⠈⠌⢂⠀⢂⢂⠄⠅⡂⡾⡽⣝⣯⡿⡯⣿⣺⢷⣻⡄⠂⢀⠠⠈⠀⠠⠀⠂⠀⠄⠀⠄⠈⠀⠐⠀⠈⠀⠀⠂⢱⠍⢿⢽⢾⢻⢽⣯⢷⣄⡀⠄⠂⠁⠀⡁⠨⠀⠄ 60 | ⢁⠐⠊⠠⠀⠂⠢⠨⢂⢂⢯⡯⣗⣯⣟⢸⡯⡢⣟⣷⡃⠠⠀⠄⡀⠂⠄⢂⠐⡀⡂⢐⠀⡂⠌⡀⢂⠡⠈⠠⠐⡸⡧⣿⢽⢧⠱⡐⢝⣯⢿⡦⢀⠠⠀⡁⠀⢐⠀⠂ 61 | ⣖⠀⡊⠨⡂⠨⣨⡪⠦⠖⡫⣏⢯⢞⡦⡽⡪⡌⣗⣯⠡⠈⡀⢁⠠⠈⡀⠄⠠⠀⠠⠀⠄⠠⠀⠄⠠⠀⡈⠀⠄⢸⠅⣪⡯⣻⠬⡬⡢⡹⡽⡉⠀⠀⡀⠂⡁⢐⠀⡁ 62 | ⠳⠑⡂⡑⠉⡐⠄⡂⡂⡡⣺⡪⣯⣫⡇⢾⡜⠬⣞⡮⡇⠠⠐⡀⠄⢂⢀⠂⡂⢁⠂⠡⠐⢐⠐⢈⠠⠐⡀⡐⢌⢜⡵⣜⢧⡻⡜⢔⢜⢼⣝⢗⠈⡀⠄⡁⡐⢐⠄⢂ 63 | ⡠⠡⡐⠄⡁⠆⠕⢌⠔⠔⠜⡞⠷⢽⠽⡜⡯⡇⡿⢝⠇⡨⢐⠠⠨⠠⢐⠠⠂⡂⠌⠄⠅⡂⠌⡐⠄⠅⠄⠪⠆⠯⠺⠝⠝⠣⠃⡓⠚⢙⠊⢃⠁⡂⠅⡂⢢⢑⢕⠁`; 64 | 65 | GM_addStyle(` 66 | #tapermonkey-app input[type=checkbox] { all: revert-layer; } 67 | `); 68 | 69 | class MISSAV_RULES { 70 | delay = 300; 71 | 72 | paginationElement = document.querySelector('a[aria-label]')?.parentElement; 73 | 74 | paginationLast = Math.max( 75 | ...Array.from(document.querySelectorAll('a[aria-label]'), (a) => Number(a.innerText)), 76 | ); 77 | 78 | CONTAINER = this.GET_THUMBS(document.body)?.[0].parentElement; 79 | 80 | paginationOffset = parseInt(new URLSearchParams(location.search).get('page')) || 1; 81 | 82 | paginationUrlGenerator = (n) => { 83 | const url = new URL(location.href); 84 | url.searchParams.set('page', n); 85 | return url.href; 86 | }; 87 | 88 | THUMB_URL(thumb) { 89 | return thumb.querySelector('a').href; 90 | } 91 | 92 | GET_THUMBS(html) { 93 | return Array.from(html.querySelectorAll('div > .thumbnail.group'), (e) => e.parentElement); 94 | } 95 | 96 | THUMB_IMG_DATA(thumb) { 97 | const img = thumb.querySelector('img.w-full'); 98 | const imgSrc = img?.getAttribute('data-src'); 99 | return { img, imgSrc }; 100 | } 101 | 102 | THUMB_DATA(thumb) { 103 | const title = sanitizeStr(thumb.querySelector('div > div > a.text-secondary')?.innerText); 104 | const duration = timeToSeconds(thumb.querySelector('div > a > span.text-xs')?.innerText); 105 | return { title, duration }; 106 | } 107 | } 108 | 109 | const RULES = new MISSAV_RULES(); 110 | 111 | //==================================================================================================== 112 | 113 | function route() { 114 | if (RULES.CONTAINER) { 115 | handleLoadedHTML(RULES.CONTAINER); 116 | } 117 | 118 | if (RULES.paginationElement) { 119 | createInfiniteScroller(store, handleLoadedHTML, RULES); 120 | } 121 | 122 | new JabroniOutfitUI(store); 123 | } 124 | 125 | //==================================================================================================== 126 | 127 | console.log(LOGO); 128 | 129 | const store = new JabroniOutfitStore(defaultStateWithDuration); 130 | const { state, stateLocale } = store; 131 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 132 | store.subscribe(applyFilters); 133 | 134 | route(); 135 | -------------------------------------------------------------------------------- /motherless.com-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Motherless.com Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 3.2.0 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration and key phrases. Download button fixed. Reveal all related galleries to video at desktop. Galleries and tags url rewritten and redirected to video/image section if available. 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://motherless.com/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=motherless.com 11 | // @grant unsafeWindow 12 | // @grant GM_addStyle 13 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 15 | // @run-at document-idle 16 | // @downloadURL https://update.sleazyfork.org/scripts/492238/Motherlesscom%20Improved.user.js 17 | // @updateURL https://update.sleazyfork.org/scripts/492238/Motherlesscom%20Improved.meta.js 18 | // ==/UserScript== 19 | /* globals $ */ 20 | 21 | const { Tick, fetchWith, timeToSeconds, replaceElementTag, isMob, sanitizeStr, getAllUniqueParents, DataManager, createInfiniteScroller } = window.bhutils; 22 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 23 | 24 | const LOGO = ` 25 | ⡿⣹⡝⣯⡝⣯⡝⣯⠽⣭⢻⣭⢻⣭⢻⣭⢻⡭⢯⡽⡭⢏⡳⣍⡛⡜⡍⢎⡱⢊⠖⡱⢊⡖⣱⢊⠶⡱⢎⠶⣩⣿⢣⠝⣺⢿⣹⣷⣿⣿⣿⣿⢠⢃⠦⡑⢢⠜⣐⠢ 26 | ⣟⡧⣟⢮⡽⣖⣻⢼⡻⣜⣳⢎⡷⣎⠷⣎⠷⣙⢧⡚⣥⢋⠶⣡⠞⣱⡘⣣⠱⣋⠼⡱⣉⠶⣡⡛⡼⣱⢫⡝⣶⣯⣏⢞⡥⢫⣝⣯⣟⣾⣿⣽⢂⠣⣌⡑⢣⡘⠤⣃ 27 | ⣞⡷⣭⢟⡾⣹⢮⢷⣹⢧⣛⠮⣕⢎⡳⢬⠳⣍⠶⣙⢦⢋⡞⣥⢚⡥⣚⠴⣙⢦⠳⣥⢣⣛⡴⣯⢵⣣⢷⣹⣿⡷⣽⣎⣿⣧⢿⣯⣿⡿⣾⠏⢆⡓⢤⡉⢖⡨⡑⢆ 28 | ⣷⡽⣺⣝⠾⣭⣛⣮⢷⣫⡽⣛⡼⣫⡝⣧⢻⣬⢳⢭⡲⣍⠶⣡⠏⡶⣹⡞⣵⢮⣟⡶⣯⣛⣾⡽⣷⡹⢎⣿⣿⣽⣷⣿⢿⣼⣻⣿⣿⢿⠏⡜⢢⢍⡒⠜⡢⡑⡜⢂ 29 | ⡵⣹⠳⣞⣻⢧⠿⣜⣧⢯⣷⣯⢷⣳⣽⣚⠷⣎⡟⣮⢳⣎⢷⣣⣛⡴⢣⡜⣩⠝⣚⠿⡹⢭⢏⡿⣶⡹⡭⣿⣯⣿⣿⠿⣛⠻⢿⣿⣿⣿⡘⣌⢣⠒⣌⢣⠱⡑⣌⠣ 30 | ⢫⡵⣛⡼⢣⡟⣯⢻⡼⢳⢮⡛⢿⢳⣟⡾⣯⢷⣹⣎⠷⣎⢧⡳⣍⡞⢧⡛⣖⢫⡜⢶⡱⣍⢮⡜⣡⢍⡱⣛⢭⡱⢦⡳⢬⣙⠶⣘⡛⢷⡘⢤⠣⢍⢆⢣⢣⠱⣌⠲ 31 | ⣟⡴⣣⡝⢧⡝⢮⣛⡜⣣⢎⡽⣌⠧⣎⡹⢫⠿⣳⣯⣟⡾⣧⢷⣺⡜⣧⡽⣬⢳⠜⣣⠚⣌⠱⡌⡱⢊⠥⣉⠞⡹⢿⡝⢦⣽⢢⠅⣏⠻⡜⢢⡙⡌⢎⢆⢣⠓⠤⠓ 32 | ⣯⣝⡳⣎⣗⡚⢧⡳⣜⡱⣎⠶⣭⢞⡶⠽⠧⠟⡶⢭⣻⡽⣯⣟⣳⣟⡷⢫⡱⣃⠞⡤⢋⠤⡓⢬⡑⣎⠶⣱⢮⡱⣣⣞⡧⣛⣬⣳⡌⢣⢍⠢⡑⢌⠢⠌⡂⠜⢠⠃ 33 | ⡷⣎⢷⡹⣎⢿⣹⠷⣜⡱⣭⢟⡎⡞⡴⣉⠎⡵⡘⢦⢡⠹⣑⡛⢬⡳⣜⢣⠳⣥⢋⠶⣉⢖⣩⢒⡹⢌⠯⡝⢶⡿⣣⣗⡷⡽⣞⣳⣭⣳⠌⢆⡑⢢⠘⡄⠱⡈⢄⠂ 34 | ⡿⣜⢧⡻⣜⢧⡻⣝⣮⢷⡘⢯⡜⡱⢜⢢⡙⠴⣉⢆⢣⡙⣤⠛⢦⣛⡬⢏⡷⢪⡝⢮⡱⢎⢆⠧⣘⠬⡒⣍⢲⡙⢷⣸⢞⡷⣯⡟⣯⢳⡿⢂⠜⡠⢃⠌⡱⠐⡌⢂ 35 | ⣷⡹⣎⢷⡹⢎⣽⣋⢯⡹⣜⢣⡜⡱⢊⠦⡙⢦⡑⢎⠦⡱⢆⡛⢦⣛⡼⣫⢞⣧⣛⢮⡵⣋⠞⣬⢱⡊⡵⡘⢦⡙⠦⣍⢚⡼⣱⢏⡟⣫⢆⠱⡨⢐⠡⢊⠔⡡⠘⡄ 36 | ⣳⢧⢻⡼⣳⡭⢶⡹⢮⡕⣎⠧⢎⡵⣉⠖⡩⢆⡹⢌⠶⣙⢬⡙⣦⢣⡟⣵⢯⣶⣛⡞⡶⣭⣛⡴⢣⠳⣥⠛⡴⣩⢓⢬⢚⡜⢣⢏⠼⣡⢎⡱⢢⢍⢢⠁⢎⠰⡁⠆ 37 | ⡿⣜⣣⣽⢗⡻⢳⣹⢣⢞⡬⢳⣩⠒⣥⢚⡱⢊⡴⢋⡼⣘⢦⢻⡴⣻⣼⢯⡿⣾⡽⣹⡗⣧⢯⣜⢯⡳⣬⣛⡴⢣⢏⡞⡜⣬⢃⢎⠳⣌⢮⡱⢃⡎⢦⡙⢦⠑⡌⣂ 38 | ⣾⡰⢧⣟⢮⢵⣫⢖⡏⣞⡜⣣⢖⡹⢤⠳⣘⢇⡞⡱⢎⡵⣋⢷⣹⢳⣞⣻⢽⣳⣟⣷⣻⡽⣞⡽⣎⢷⡳⣎⢷⢫⡞⡼⡱⢆⡫⣌⠳⡜⢦⡝⢣⠞⣢⡙⢆⢣⠒⡌ 39 | ⣗⣯⡷⣹⢮⡳⣎⢷⡹⣎⡼⡱⢎⡵⣊⠷⣩⠞⡼⣱⢫⢶⡹⣎⢷⣫⢾⣭⢿⣳⣟⣾⣳⢿⡽⣯⡽⣣⢟⡼⣋⡧⣝⠶⣙⢮⡑⣎⢳⡙⣦⠍⣇⢫⠴⣙⠬⡒⢩⡐ 40 | ⣿⢾⣟⡯⢷⣝⡮⢷⣹⢶⣙⢧⡻⣴⢋⡞⣥⢻⡜⣧⣛⣮⢷⣫⢷⣫⣟⡾⣯⢷⣞⡷⣿⣟⣿⣣⢟⡽⣎⢷⡹⢎⣧⢛⡜⡦⡝⣬⢣⡝⢦⢋⡔⢣⠚⡄⠓⡌⢅⠂ 41 | ⣿⣻⡼⡽⣏⡾⣝⡿⣜⣧⢻⣎⡷⣭⡻⣜⣳⣏⣾⣳⡽⣞⣯⣳⢯⢶⣯⣽⣯⣟⣾⣻⣿⡽⣶⣛⢮⢳⡎⢷⣩⢏⠶⣩⢞⣱⡙⡦⢏⡼⣋⠖⣌⠣⢍⠢⡑⢌⣂⠣ 42 | ⣷⣳⡽⣽⣫⣽⣻⣼⣻⣼⣻⢞⡷⣯⡽⣯⢷⣞⡷⣯⢿⣽⣞⡷⣯⣟⣾⣽⣾⣻⣾⣿⢯⣟⢶⡹⣎⣗⢺⢣⡞⡼⣩⣓⢮⢲⣍⡳⢏⡞⣥⢋⠤⢋⠬⡱⡘⠔⠢⡅ 43 | ⣷⣻⢞⣷⣛⡾⣵⣳⣟⡾⣽⢯⣟⣷⣻⣽⣻⢾⣽⣟⣿⣻⣾⣿⣟⣾⣿⣽⣷⣿⣻⣯⣟⢮⡳⣝⠶⣪⢭⣓⢮⠵⡳⡜⣮⠳⣜⡝⣮⡝⡦⢽⣅⢋⢆⠱⣈⢎⡱⢄ 44 | ⣷⢯⣟⡾⣽⣻⢷⣻⢾⡽⣯⣟⣾⣳⢟⡾⣽⣻⢾⡽⣞⡿⣽⣿⣿⣿⣾⣿⣯⣿⣿⣳⢏⡷⣝⢮⣛⣥⢳⣎⣏⢾⡱⣛⡴⡻⣜⡞⣵⢺⢩⠃⢏⡸⢌⢒⡡⢂⠖⣈ 45 | ⣯⣟⡾⣽⣳⢯⡿⣽⢯⣟⣷⢻⣞⣭⢿⣹⠶⣏⡿⣽⢯⣟⡿⣿⣿⣿⣿⣿⣿⣿⢷⣯⣛⢾⡹⣎⠷⣎⢷⡺⣜⢧⣛⣥⢻⡵⣣⢟⡼⣋⠦⡙⠰⢂⠎⠢⠔⡡⢊⠄ 46 | ⡿⣽⣻⢷⣯⠿⣽⣳⣟⡾⣞⠿⣼⢎⡷⣭⢻⡞⣽⣳⣟⡾⣿⣿⣿⣿⣽⡷⢾⣽⣻⢶⣫⣗⣻⣜⡻⣜⢧⡻⣜⢧⡻⣜⡳⣞⡵⣫⢞⣥⣶⣷⣿⣶⣿⣿⣿⣿⣿⣿`; 47 | 48 | // Enable internal downloader 49 | unsafeWindow.__is_premium = true; 50 | 51 | class MOTHERLESS_RULES { 52 | delay = 150; 53 | 54 | IS_SEARCH = /^\/term\//.test(location.pathname); 55 | CONTAINER = [...document.querySelectorAll('.content-inner')].pop(); 56 | 57 | paginationElement = document.querySelector('.pagination_link, .ml-pagination'); 58 | paginationLast = parseInt( 59 | document 60 | .querySelector('.pagination_link a:last-child') 61 | ?.previousSibling.innerText.replace(',', '') || 62 | document.querySelector('.ml-pagination li:last-child')?.innerText.replace(',', ''), 63 | ); 64 | 65 | paginationOffset = parseInt(new URLSearchParams(location.search).get('page')) || 1; 66 | 67 | paginationUrlGenerator = (offset) => { 68 | const url = new URL(location.href); 69 | url.searchParams.set('page', offset); 70 | return url.href; 71 | }; 72 | 73 | GET_THUMBS(html) { 74 | return html.querySelectorAll('.thumb-container, .mobile-thumb'); 75 | } 76 | 77 | THUMB_URL(thumb) { 78 | return thumb.querySelector('a').href; 79 | } 80 | 81 | THUMB_DATA(thumb) { 82 | const uploader = sanitizeStr(thumb.querySelector('.uploader')?.innerText); 83 | const title = sanitizeStr(thumb.querySelector('.title')?.innerText).concat(` user:${uploader}`); 84 | const duration = timeToSeconds(thumb.querySelector('.size')?.innerText); 85 | return { title, duration }; 86 | } 87 | 88 | THUMB_IMG_DATA(thumb) { 89 | const img = thumb.querySelector('.static'); 90 | const imgSrc = img.getAttribute('src'); 91 | return { img, imgSrc }; 92 | } 93 | } 94 | 95 | const RULES = new MOTHERLESS_RULES(); 96 | 97 | //==================================================================================================== 98 | 99 | function animate() { 100 | $(RULES.CONTAINER).find('a, div, span, ul, li, p, button').off(); 101 | const ANIMATION_INTERVAL = 500; 102 | const tick = new Tick(ANIMATION_INTERVAL); 103 | let container; 104 | 105 | function handleLeave(e) { 106 | tick.stop(); 107 | const preview = e.target.className.includes('desktop') 108 | ? e.target.querySelector('.static') 109 | : e.target.classList.contains('static') 110 | ? e.target 111 | : undefined; 112 | $(preview.nextElementSibling).hide(); 113 | preview.classList.remove('animating'); 114 | } 115 | 116 | function handleOn(e) { 117 | const { target, type } = e; 118 | if ( 119 | !(target.tagName === 'IMG' && target.classList.contains('static')) || 120 | target.classList.contains('animating') || 121 | target.parentElement.parentElement.classList.contains('image') || 122 | target.getAttribute('src') === target.getAttribute('data-strip-src') 123 | ) return; 124 | target.classList.toggle('animating'); 125 | 126 | container = target.parentElement.parentElement; 127 | const eventType = type === 'mouseover' ? 'mouseleave' : 'touchend'; 128 | container.addEventListener(eventType, handleLeave, { once: true }); 129 | 130 | let j = 0; 131 | const d = $(container.querySelector('.img-container')); 132 | const m = $( 133 | target.nextElementSibling || 134 | '
    ', 135 | ); 136 | if (!target.nextElementSibling) { 137 | $(target.parentElement).append(m); 138 | } 139 | const c = $(target); 140 | const stripSrc = target.getAttribute('data-strip-src'); 141 | m.show(); 142 | 143 | tick.start(() => { 144 | const widthRatio = Math.floor((1000.303 * c.width()) / 100); 145 | const heightRatio = Math.floor((228.6666 * c.height()) / 100); 146 | 147 | m.css({ 148 | width: d.width(), 149 | height: c.height(), 150 | 'background-image': `url('${stripSrc}')`, 151 | 'background-size': `${widthRatio}px ${heightRatio}px`, 152 | 'background-position': `${(j++ * d.width()) % widthRatio}px 0`, 153 | }); 154 | }); 155 | } 156 | 157 | document.body.addEventListener('mouseover', handleOn); 158 | document.body.addEventListener('touchstart', handleOn); 159 | } 160 | 161 | //==================================================================================================== 162 | 163 | function fixURLs() { 164 | document.querySelector('a[href^="https://motherless.com/random/image"]').href = 165 | 'https://motherless.com/m/calypso_jim_asi'; 166 | document.querySelectorAll('.gallery-container').forEach((g) => { 167 | const hasVideos = parseInt(g.innerText.match(/([\d|\.]+)k? videos/gi)?.[0]) > 0; 168 | const header = hasVideos ? '/GV' : '/GI'; 169 | g.querySelectorAll('a').forEach((a) => { 170 | a.href = a.href.replace(/\/G/, () => header); 171 | }); 172 | }); 173 | document.querySelectorAll('a[href^="/term/"]').forEach((a) => { 174 | a.href = a.href.replace(/[\w|+]+$/, (v) => `videos/${v}?term=${v}&range=0&size=0&sort=date`); 175 | }); 176 | document.querySelectorAll('#media-groups-container a[href^="/g/"]').forEach((a) => { 177 | a.href = a.href.replace(/\/g\//, '/gv/'); 178 | }); 179 | } 180 | 181 | //==================================================================================================== 182 | 183 | function mobileGalleryToDesktop(e) { 184 | e.querySelector('.clear-left').remove(); 185 | e.firstElementChild.appendChild(e.firstElementChild.nextElementSibling); 186 | e.className = 'thumb-container gallery-container'; 187 | e.firstElementChild.className = 'desktop-thumb image medium'; 188 | e.firstElementChild.firstElementChild.nextElementSibling.className = 'gallery-captions'; 189 | replaceElementTag(e.firstElementChild.firstElementChild, 'a'); 190 | return e; 191 | } 192 | 193 | async function desktopAddMobGalleries() { 194 | const galleries = document.querySelector('.media-related-galleries'); 195 | if (galleries) { 196 | const galleriesContainer = galleries.querySelector('.content-inner'); 197 | const galleriesCount = galleries.querySelectorAll('.gallery-container').length; 198 | const mobDom = await fetchWith(window.location.href, { html: true, mobile: true }); 199 | const mobGalleries = mobDom.querySelectorAll('.ml-gallery-thumb'); 200 | for (const [i, x] of mobGalleries.entries()) { 201 | if (i > galleriesCount - 1) { 202 | galleriesContainer.append(mobileGalleryToDesktop(x)); 203 | } 204 | } 205 | } 206 | } 207 | 208 | //==================================================================================================== 209 | 210 | GM_addStyle(` 211 | .img-container, .desktop-thumb { min-height: 150px; max-height: 150px; } 212 | 213 | .group-minibio, .gallery-container { display: block !important; } 214 | 215 | @media only screen and (max-width: 1280px) { 216 | #categories-page.inner .filtered-duration { display: none !important; } 217 | #categories-page.inner .filtered-exclude { display: none !important; } 218 | #categories-page.inner .filtered-include { display: none !important; } 219 | } 220 | 221 | .ml-masonry-images.masonry-columns-4 .content-inner { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); } 222 | .ml-masonry-images.masonry-columns-6 .content-inner { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); } 223 | .ml-masonry-images.masonry-columns-8 .content-inner { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); } 224 | `); 225 | 226 | //==================================================================================================== 227 | 228 | function applySearchFilters() { 229 | let pathname = window.location.pathname; 230 | const wordsToFilter = 231 | state.filterExcludeWords.replace(/f\:/g, '').match(/(? !pathname.includes(w)) 234 | .forEach((w) => { 235 | pathname += `+-${w.trim()}`; 236 | }); 237 | if (wordsToFilter.some((w) => !window.location.href.includes(w))) { 238 | window.location.href = pathname; 239 | } 240 | } 241 | 242 | //==================================================================================================== 243 | 244 | function route() { 245 | desktopAddMobGalleries().then(() => fixURLs()); 246 | 247 | if (RULES.paginationElement) { 248 | createInfiniteScroller(store, handleLoadedHTML, RULES); 249 | animate(); 250 | } 251 | 252 | if (RULES.GET_THUMBS(document.body).length > 0) { 253 | new JabroniOutfitUI(store); 254 | getAllUniqueParents(RULES.GET_THUMBS(document.body)).forEach((c) => { 255 | handleLoadedHTML(c, c, true); 256 | }); 257 | } 258 | 259 | if (RULES.IS_SEARCH) { 260 | applySearchFilters(); 261 | } 262 | } 263 | 264 | //==================================================================================================== 265 | 266 | console.log(LOGO); 267 | 268 | const store = new JabroniOutfitStore(defaultStateWithDuration); 269 | const { state, stateLocale } = store; 270 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 271 | store.subscribe(applyFilters); 272 | 273 | route(); 274 | -------------------------------------------------------------------------------- /namethatporn.com.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name NamethatPorn Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0.0 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by solved/unsolved, include/exclude phrases. 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://namethatporn.*/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=namethatporn.com 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.4/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @run-at document-idle 15 | // ==/UserScript== 16 | 17 | const { timeToSeconds, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 18 | const { JabroniOutfitStore, JabroniOutfitUI, defaultSchemeWithPrivateFilter, defaultStateWithDurationAndPrivacy } = window.jabronioutfit; 19 | 20 | const LOGO = ` 21 | ⡳⣝⢮⡳⣝⢮⡳⣝⢮⢳⡣⣗⡳⡳⡳⣕⢗⣝⢮⡳⡽⣯⡿⣽⢯⡿⣽⡯⣿⢽⢿⢽⢯⢿⢽⢽⣫⢯⢯⢯⢯⢯⢯⣗⢯⣟⢮⢗⢽⡘⡐⣞⣯⢿⡽⣽⣺⣝⢮⡳ 22 | ⣻⢺⣕⢯⡳⣝⡞⣮⡳⣝⡞⣼⢪⣏⢗⡵⣝⢮⡳⣝⡾⡯⣟⡯⣟⡽⡵⡯⣳⡫⡯⡫⣗⢽⢝⣕⢯⡫⣏⢯⣳⣫⡳⡵⣝⢮⡫⣏⢧⢅⣸⣽⣞⡯⣟⣞⡞⡮⡳⡱ 23 | ⡷⣝⢮⣳⡫⣗⢽⡪⣞⢵⢝⡮⡳⣕⣗⢽⣪⣳⢽⡺⡺⡽⣕⡯⡮⣫⢺⢪⡺⢜⢎⢏⢮⢳⢕⢗⢵⢝⢮⢳⢕⣗⢽⡪⣗⢽⡺⣪⡳⣕⢕⡳⣯⢯⣗⢧⢯⢪⢣⠃ 24 | ⣽⡺⣕⢷⢝⢾⢕⡯⣺⢝⡵⡽⣝⢮⢮⣗⡗⣗⣝⢮⢯⢯⢺⢜⢕⢕⢝⡜⡜⡕⡕⡇⡗⡕⣕⢝⡜⡵⡹⣕⡳⡕⣗⢝⢮⡳⡝⡮⡪⣎⢧⢳⠸⣝⢮⡳⡱⡱⢁⠊ 25 | ⡷⣝⢷⢽⢽⢽⢕⡯⣺⢵⡫⣞⡕⡯⡳⢵⢝⢮⢪⡳⣝⢮⢣⢣⢣⢣⢣⢪⢪⠪⡪⢪⠪⡪⡪⡪⡪⣪⡚⡖⣕⢽⢸⢕⢗⢵⢝⢎⢧⡣⡇⣇⢳⠨⢳⠱⡑⠌⠠⠀ 26 | ⣟⣞⢽⢝⣞⢽⣕⢯⡳⡳⡹⡜⡜⡎⡎⡇⡗⡕⡇⡯⣪⢪⢪⠪⡪⢊⠢⡃⢎⢊⢪⠸⡘⢜⢌⢎⢎⢖⢕⢝⢜⢎⢗⢭⣓⢧⢫⢎⢧⢳⢱⢱⡑⢌⢐⠱⠨⠐⠀⠀ 27 | ⣗⣗⢯⢯⣞⢗⡳⡕⡇⡏⡎⡎⡪⢪⠸⡘⡌⡎⢎⢞⢜⠜⡐⠕⢌⠪⡨⢊⠢⡡⠅⠕⡅⢇⢎⢢⠣⡣⡣⡣⡳⡹⡸⡕⡮⣪⢳⡹⣪⢣⢫⢲⢑⢕⢐⠈⠌⠄⡁⠀ 28 | ⣗⣗⢯⡗⡗⡇⡗⡕⢕⢑⡑⢌⢊⠢⡑⡌⢆⢣⢣⢣⠱⡨⢊⠜⡠⢑⠄⠕⡈⡢⢑⢑⠌⢆⠥⡑⡅⢇⢎⢎⢮⢪⡣⡳⡹⡜⣎⢞⡜⡎⣇⢇⢇⢇⢪⢐⠈⣂⢂⠁ 29 | ⡷⣝⡗⡝⡎⡎⢎⠜⡨⢂⢊⠢⠡⡑⢌⠢⡑⡕⡅⡣⡑⠌⠔⡨⢐⠐⠌⠌⠢⠨⡂⠅⢕⢑⠌⢆⢎⢪⢢⢣⢣⢣⢣⡫⡺⣸⢪⢎⢮⢝⡜⣜⢕⢕⢕⠕⡌⠔⠔⠀ 30 | ⣯⡳⣝⢜⢜⠌⢆⠕⡈⡂⠢⠡⢑⠨⠐⢅⠪⡢⡃⢆⢊⠌⡂⡂⡂⠅⠅⢅⢑⠡⠊⢌⠢⠢⡑⢅⠆⡕⢅⢣⢱⢱⢱⡱⣹⢸⢜⢮⢺⢜⢎⡞⣜⢕⢵⢱⢱⠡⠅⠄ 31 | ⡳⡝⡜⡜⡌⡪⢂⠅⡂⡊⠌⠨⢐⠨⡈⡢⢱⠱⡨⢂⢂⢂⢂⠂⡂⠅⡁⡂⠢⠡⠡⠡⡊⢌⠌⢆⠕⢜⢘⢜⢜⢜⢜⡜⣜⢎⢇⡗⡵⣝⢜⢮⢺⢪⡣⣳⢱⡹⡨⡀ 32 | ⡏⣞⢜⢜⢌⠢⡡⢂⢂⠂⠅⠅⡂⡂⠢⡊⢎⢌⠢⠡⢂⢂⠢⠨⠐⡈⠄⠌⠌⢌⢐⠡⡈⡢⢡⠱⡘⢌⠪⡢⡣⡣⡣⡣⣣⢳⢣⡫⣺⢸⢕⢽⡱⡳⣹⡪⡺⡜⡎⡆ 33 | ⣝⢜⡜⡔⠥⡑⠔⡁⠔⢈⠄⠅⡐⠠⡑⡌⡎⡢⠣⡑⡁⠢⡈⡂⠅⠂⠅⢌⢐⢐⢐⠨⢐⠨⡐⢅⠪⡘⡌⡆⡇⡎⡎⡮⡪⡮⡳⡹⣜⢵⢝⢵⢝⢼⡪⡮⣳⢹⢪⡪ 34 | ⡪⡇⡇⡎⡕⢅⠕⠠⡁⡂⢂⢁⢂⢑⠰⡸⡨⡸⡈⡢⢊⠔⡐⠄⠅⠅⠅⡂⡐⠄⡂⢌⢐⢁⠪⡐⢅⠕⡌⢆⢣⢣⢣⡣⣳⢱⢝⡺⣪⢳⡹⡵⣝⣕⢗⡽⢼⢕⢧⠳ 35 | ⢵⡹⡸⡨⡢⡱⢨⠨⡀⡂⡂⡂⡂⡢⡑⢜⢌⢆⠎⡢⠡⡂⠢⠡⠡⢑⢐⠐⠄⢅⠐⡐⡐⡐⢅⢊⢢⠱⡘⡜⢜⢜⢜⢜⡜⣎⢧⡫⡮⡳⣝⢮⡳⣕⢗⡽⡵⣝⢮⢫ 36 | ⢧⢳⡱⡱⣑⢌⠢⡑⡄⡂⡂⡂⡢⠢⡑⡕⡕⡅⡕⢌⠪⢐⠡⡡⠡⡡⠂⢅⠑⠄⢅⢂⠢⡈⡢⢊⢢⠱⡑⡜⡜⡜⡜⡕⣝⠼⡜⣎⣗⢽⣪⡳⣝⡮⣗⢽⡺⣚⢮⡣ 37 | ⣹⢪⡪⡪⡢⡣⢱⠨⡢⢑⢐⢌⢂⠇⡕⡕⣕⢕⢜⢔⢑⠅⢕⢐⠅⡢⢑⠡⢊⠌⡂⡢⠡⡂⡪⡨⢢⢃⢣⢣⢣⢣⢫⢺⡸⡹⣜⢮⢮⡳⡵⣝⣞⢞⡮⣳⢽⡪⣗⡝ 38 | ⢕⢧⢳⡱⡱⡱⡡⡃⡎⢌⢆⠕⡌⢎⢪⢪⡪⣎⢎⢎⢜⢸⠨⡢⠱⡨⡂⡣⡑⢌⠢⡊⡌⢆⠕⡜⢌⢎⢪⢪⢪⡪⡎⡧⣫⡺⡪⣞⢵⡫⣞⢵⡳⣝⡮⣗⢷⢝⡮⡺ 39 | ⣏⢗⢧⢳⡹⣸⢸⢸⢘⢔⢅⢇⢎⢎⢇⢗⣝⢮⢧⡳⣱⢱⢱⢱⢑⢕⢌⢆⠪⡂⢇⢪⢘⠔⡕⡱⡱⡱⡱⡱⣱⢱⢝⣜⢮⡺⣝⢮⢗⡽⣺⢽⢝⣞⡮⡷⣝⢷⢽⢕ 40 | ⣺⢝⡵⡳⣝⢜⢮⢺⢸⡸⡸⣸⢸⡪⡳⣕⢷⣝⣟⣾⣺⣎⣧⡳⣕⢕⢕⢕⢕⢕⢕⢕⢱⢱⢱⢱⢱⢸⢸⢪⡺⣜⢵⢕⢧⠯⡮⣫⢗⡯⣞⡽⡵⣳⣫⢯⡺⣝⣮⡳ 41 | ⣯⣗⡯⡯⡮⣏⣗⢽⣱⡳⣝⢼⡪⣞⣽⣺⣽⡾⣿⣿⣿⡳⣳⣝⢮⡳⡝⣜⢜⢜⢜⢜⠜⡜⡜⡜⣜⢜⢵⡱⡵⡕⣗⢽⢕⡯⣫⢞⡽⣪⢷⢽⣝⣗⣝⢷⣝⣗⢧⢇ 42 | ⢵⡳⡯⣯⣻⣺⢾⢽⣺⢾⢽⡽⡽⣗⡿⣞⣷⢿⣿⡿⡮⡯⡺⣜⢵⢝⢞⢎⡗⣝⢜⢼⢸⢪⡪⡎⡮⣪⡣⢧⡳⣝⢎⣗⢽⡪⣗⡽⣺⢵⣫⣗⣗⢷⢝⣗⣗⢷⢽⠸ 43 | ⢿⣝⢽⣺⣺⡪⡯⡯⣺⢽⣕⢯⡻⡮⡯⣟⣾⢿⣿⢽⢽⢝⡽⡜⣎⢮⢳⢕⢧⢳⢹⡸⣱⢣⡣⡳⣹⡸⡼⡕⣗⢵⡳⡽⣕⣯⡳⣝⡮⣗⡧⣗⡾⡵⣯⣲⡳⡧⡯⢢`; 44 | 45 | class NAMETHATPORN_RULES { 46 | delay = 350; 47 | 48 | constructor() { 49 | this.paginationElement = document.querySelector('#smi_wrp, #nsw_p'); 50 | this.paginationLast = parseInt([...document.querySelectorAll('#smi_wrp a, #nsw_p a')].pop()?.href.match(/\d+/)?.[0] || 1); 51 | Object.assign(this, this.URL_DATA()); 52 | this.CONTAINER = document.querySelector('#items_wrapper, #nsw_r'); 53 | } 54 | 55 | IS_PRIVATE(thumb) { 56 | return !!thumb.querySelector('.item_solved, .nsw_r_slvd'); 57 | } 58 | 59 | THUMB_URL(thumb) { 60 | return thumb.querySelector('a')?.href || thumb.href; 61 | } 62 | 63 | GET_THUMBS(html) { 64 | return [...html.querySelectorAll('.item, .nsw_r_w')]; 65 | } 66 | 67 | THUMB_IMG_DATA(thumb) { 68 | const img = thumb.querySelector('img'); 69 | const imgSrc = img.getAttribute('data-dyn')?.concat('.webp') || img.getAttribute('src'); 70 | return ({ img, imgSrc }); 71 | } 72 | 73 | THUMB_DATA(thumb) { 74 | const item_answer = sanitizeStr(thumb.querySelector('.item_answer b, .nsw_r_desc')?.innerText); 75 | const item_title = sanitizeStr(thumb.querySelector('.item_title, .nsw_r_tit')?.innerText); 76 | const title = `${item_title} ${item_answer}`; 77 | const duration = 0; 78 | return { title, duration }; 79 | } 80 | 81 | URL_DATA() { 82 | const url = new URL(location.href); 83 | const paginationOffset = parseInt(url.searchParams.get('page')) || 1; 84 | 85 | const paginationUrlGenerator = n => { 86 | url.searchParams.set('page', n); 87 | return url.href; 88 | } 89 | 90 | return { paginationOffset, paginationUrlGenerator } 91 | } 92 | } 93 | 94 | const RULES = new NAMETHATPORN_RULES(); 95 | 96 | //==================================================================================================== 97 | 98 | function router() { 99 | if (RULES.CONTAINER) { 100 | handleLoadedHTML(RULES.CONTAINER); 101 | } 102 | 103 | if (RULES.paginationElement) { 104 | store.localState = store.stateLocale; 105 | createInfiniteScroller(store, handleLoadedHTML, RULES); 106 | } 107 | 108 | delete defaultSchemeWithPrivateFilter.durationFilter; 109 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 110 | } 111 | 112 | //==================================================================================================== 113 | 114 | defaultSchemeWithPrivateFilter.privateFilter = [ 115 | { type: "checkbox", model: "state.filterPrivate", label: "unsolved" }, 116 | { type: "checkbox", model: "state.filterPublic", label: "solved" }]; 117 | 118 | //==================================================================================================== 119 | 120 | console.log(LOGO); 121 | 122 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 123 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 124 | store.subscribe(applyFilters); 125 | router(); 126 | -------------------------------------------------------------------------------- /nhentai-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name NHentai Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 2.2.0 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by include/exclude phrases and languages. Search similar button 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.nhentai.*/* 10 | // @match https://*.3hentai.net/* 11 | // @match https://*.e-hentai.org/* 12 | // @icon https://www.google.com/s2/favicons?sz=64&domain=nhentai.net 13 | // @grant GM_addStyle 14 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 15 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 16 | // @run-at document-idle 17 | // @downloadURL https://update.sleazyfork.org/scripts/499435/NHentai%20Improved.user.js 18 | // @updateURL https://update.sleazyfork.org/scripts/499435/NHentai%20Improved.meta.js 19 | // ==/UserScript== 20 | 21 | const { parseDom, sanitizeStr, DataManager, InfiniteScroller, createInfiniteScroller } = 22 | window.bhutils; 23 | const { JabroniOutfitStore, defaultStateInclExclMiscPagination, JabroniOutfitUI, DefaultScheme } = 24 | window.jabronioutfit; 25 | 26 | const LOGO = `⠡⠡⠡⠡⠡⠅⠅⢥⢡⢡⢠⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⠡⡥⠨⡨⠈⠌⠌⠌⠌⠌⠌⡐ 27 | ⠡⢡⡃⡅⠅⠅⢅⢦⣂⡂⡒⡜⡈⡚⡂⡥⠡⠡⠡⠡⠡⠡⠡⠡⡡⠡⠡⠑⠅⠣⣕⠡⠡⡡⠡⠡⠡⠡⠡⠡⣡⣡⡡⠡⠡⡑⢑⠡⠡⠡⠡⠩⠩⠨⠩⢹⠨⠨⢐⢐ 28 | ⠨⢐⠐⠅⠥⠡⠇⡂⡑⡐⡈⠢⠬⢑⢨⠨⠨⠨⠨⠨⢨⢘⠨⢑⠠⠡⠡⠡⠡⠡⠨⢈⠢⠨⠱⡡⠡⠡⠡⠡⣳⣳⡣⠡⢁⢓⠒⠄⢅⣱⠡⢩⡩⠩⢩⢉⠪⢌⢐⢐ 29 | ⠌⡐⠨⠨⠨⠨⡒⣤⣌⡐⠠⢑⢑⢤⠃⠌⠌⠌⠌⢌⢂⢂⢂⠂⠌⠌⠌⠌⢌⠌⠌⡐⠨⠨⠢⡘⠌⠌⠌⢰⠒⡖⠢⢁⢂⠢⠑⠩⣐⢐⠨⠰⠠⡩⢐⠪⡊⠔⡁⡂ 30 | ⠅⡂⢅⠇⠡⢑⠨⢐⠠⢑⠣⢌⠢⡂⠍⡪⠨⠨⠈⡆⡂⡂⡂⠌⠌⡪⠨⢈⢢⠃⠅⠌⠌⠨⠨⠨⢘⠨⠨⢘⣘⡚⡃⡂⢢⠡⢡⢁⠢⢸⠨⡈⠢⠨⢱⠠⡡⢁⢂⢂ 31 | ⢐⢐⠠⢡⢑⠐⡨⢊⡱⢐⠨⡘⡐⡈⡊⠄⠅⠅⠅⢣⢃⢢⢱⢭⡢⣂⢑⢑⠢⠡⠡⡡⠡⠡⠱⠡⢱⢈⠨⢸⢅⣻⣇⢂⢂⠪⡠⡁⠍⠅⡄⠣⠡⡑⡁⡢⡑⡐⡐⡐ 32 | ⢂⠂⠌⡐⠠⠩⡨⣈⡒⡔⠅⡂⡂⡂⡂⠅⠅⠅⠅⠌⡊⠢⠲⢙⢘⠨⠑⠕⡑⠕⡕⢄⢅⢃⢱⢱⠐⡐⠨⠰⡖⡶⠲⢐⢐⢐⢐⠠⢡⣑⢂⠱⠡⠨⠱⠰⢐⢐⢐⠠ 33 | ⢐⠨⢐⠠⠡⠡⠱⡐⠠⠩⡩⡨⣁⢂⠂⠅⠅⠅⠅⠅⡂⠣⡁⢪⢐⠡⠡⠡⠨⠠⢁⢂⢂⢂⠆⡢⢂⠂⠅⣑⡑⣋⡊⡐⡐⡐⡅⣅⡂⠔⢔⠨⠬⡈⣊⢊⣒⡐⡐⠨ 34 | ⡂⠌⡐⢨⠨⠈⡂⡃⠅⠅⠣⠰⠤⡂⠌⠌⠌⠌⠌⡐⠠⠡⠘⠔⢔⠌⠄⡕⡡⡁⢆⢲⢐⠜⡔⡁⡂⠌⡐⣸⢋⢟⢕⢐⢐⢐⢐⢒⠠⡩⡩⠨⢐⣐⢀⡂⡂⠇⡂⠅ 35 | ⠂⠅⡂⡘⠬⣐⣘⢨⢨⡨⠜⡊⡒⠠⠡⠡⠡⠡⢁⠂⠅⠅⣌⢌⢂⡕⣙⢂⢑⢁⠂⡂⠄⠌⡐⠰⡀⠅⡂⡂⡂⡂⡂⡂⡂⡂⣂⠕⡁⠅⠍⠌⠌⡐⡐⢒⢨⢐⠠⠡ 36 | ⠨⢐⠰⡠⢑⠒⡂⡂⠢⡂⢅⠆⠢⢡⠡⠡⠡⢁⢂⢌⠪⡑⡐⡐⡐⢌⠢⡈⡒⠬⢩⠩⠫⠩⠨⢡⠨⡲⠰⠰⡐⡐⡐⡐⠠⢐⢐⢀⠪⠬⠨⠨⡘⠨⢁⢒⢊⢐⠨⠨ 37 | ⠨⢐⠠⢈⢂⠱⢁⢂⠸⠠⠝⡀⠅⣂⢍⠸⠨⣐⡑⢄⢑⢐⢐⢐⢌⠢⡑⡐⠌⢌⠢⠡⠡⢅⢃⠢⡑⠌⢌⢂⠢⡈⠢⡂⠅⡂⡂⣑⣊⠌⡊⡊⢰⢁⢑⢐⢐⠐⠨⠨ 38 | ⠌⡐⠨⢐⢐⢘⠰⠔⠨⠨⠰⢐⢁⢂⠂⠌⡐⡨⠅⢕⢢⠢⡢⡑⡐⡑⡐⠌⢌⠢⠡⡁⡃⠢⠣⡑⠌⢌⢂⢢⢑⢌⠢⠨⡐⠐⠀⡇⢸⠰⠂⠌⠍⡐⠠⣁⡂⡅⠅⠅ 39 | ⠅⡂⢅⢒⢒⢂⠊⠌⠌⠌⠌⡐⡐⡐⠌⡐⡐⡆⠅⠢⠱⡣⡊⡢⡃⠢⠨⡈⠢⠡⡑⡜⢌⢘⢐⢸⢈⢂⢂⠂⢇⢅⢅⢝⠠⠡⠁⡇⢸⠨⠩⠩⢁⠂⢅⢂⢂⠂⠅⠅ 40 | ⢁⠂⢌⢂⠢⠢⠃⠅⠅⡑⡉⡃⡋⡊⢲⢀⢂⡣⠡⢑⢡⠋⣆⡎⠌⢌⠢⠨⡈⠢⣨⢊⠢⠨⡂⣺⢰⠐⠄⠅⢍⠦⠇⠱⠨⠨⠀⡇⠘⡈⠌⠨⡘⡐⠸⢐⢐⠨⠨⢐ 41 | ⢐⠨⠸⠰⠨⡌⠌⠌⡐⠆⠢⠡⠡⠡⢁⠂⢎⠌⠌⡐⠌⠠⢱⠪⡈⠢⠡⡑⠌⡌⡎⠢⠡⡑⡐⢌⢎⢎⠌⠌⢜⠘⢌⢌⠌⠌⠄⠥⢑⠠⠡⠅⡑⡒⡁⡂⡂⠌⠌⡐ 42 | ⡂⠌⠌⠌⡄⡑⠡⡑⡂⡓⣑⢑⢡⢁⠂⡌⡂⡂⢅⠊⠌⠌⢪⠢⠨⡈⡢⡨⡪⡪⠪⡈⡂⠆⢌⢂⢣⠱⡑⡅⢕⠨⡐⠢⡃⠅⠅⠅⡐⡈⢊⢂⢐⠐⡐⡐⠨⠨⡁⡂ 43 | ⡂⠅⠅⠕⡐⠠⡡⠅⢦⠨⢐⠠⠨⢒⢰⢁⢂⠂⡌⠌⠌⠌⢌⢌⢆⠪⡪⡪⡪⡊⡂⡂⠪⡊⡂⡢⡑⡕⢕⢕⠔⢀⠃⠅⠅⠅⠅⠅⡂⡒⡲⢐⢐⢐⠢⡨⠨⠈⠅⡂ 44 | ⠂⠅⠅⠅⠌⠠⠡⡡⠂⠌⡐⠜⠨⠠⠨⠐⡄⡂⡃⠅⠅⢅⢱⢱⢱⢱⢨⠪⠪⡊⡊⡊⠪⠢⠢⡘⡔⡘⢔⢕⠅⠄⠨⠨⢈⢊⠌⣐⢐⠨⢐⡐⡐⡢⡠⠨⠨⢌⢐⠠ 45 | ⠨⠨⠨⡠⢃⠃⠍⡂⡃⡓⠬⠌⡎⠅⢱⢁⢢⠁⡎⠌⡐⡐⡌⣖⠵⡑⠅⢅⢑⡐⡔⡌⡌⡌⡂⡂⡊⠎⡆⡕⠌⠄⠅⡑⡐⠔⠔⡁⡂⡼⠡⢃⠂⠥⠨⡌⢌⠄⢱⠨ 46 | ⠌⢌⠌⡐⣐⢘⢓⢑⢐⠠⠡⠡⡑⡑⡘⡐⡘⡀⡇⢅⢂⢢⢑⢅⢕⣬⡣⣣⢣⢣⢣⡣⡣⡓⣕⢕⢌⢌⢌⠢⠅⢅⠓⠌⠄⠅⡑⡊⡀⡃⡑⡆⠌⠌⠌⡐⡀⠅⡁⡂ 47 | ⠅⡑⡐⡐⡁⡂⡢⢊⢐⠨⡨⢊⢐⢐⢐⢐⢐⢀⢃⢂⢘⢧⢣⢫⢣⢣⣑⡐⡑⡐⡑⢌⠪⠨⠢⡑⡙⡔⠧⢧⢑⢡⠡⠡⠡⢁⢂⣂⡂⣂⠂⡅⢅⢅⠅⠢⣢⢅⢂⢂ 48 | ⢐⢐⢐⠔⡐⠤⢑⠂⠆⠍⡐⡐⡐⡐⡐⡐⡐⡐⣐⡰⢎⠚⠅⢍⠪⢣⠲⢨⢊⠪⠩⢣⠩⡓⡑⢔⢢⢝⠕⢗⢕⢔⢈⠎⠌⡐⠠⣱⠹⣀⠇⡇⢸⣸⠨⢀⢧⡻⢐⢐ 49 | ⢐⠠⢑⠰⠄⢅⡢⢑⠡⣁⣂⠂⢆⢂⢂⡢⡒⡑⡐⢌⠢⠡⡑⢄⠑⠄⠅⠕⢌⢚⢬⡂⢣⠪⡨⡢⡣⡑⢅⢑⠐⠄⢅⢑⢑⠢⢅⢂⢂⢂⢂⢂⡂⡂⣌⣐⢐⠨⢐⢐ 50 | ⢐⠨⠐⡬⠌⡒⠨⠨⢐⢐⡐⢬⢘⣰⢜⢜⢌⢂⢊⠢⠡⡑⢌⠢⠡⠡⠡⡑⡐⢌⠢⢫⢪⢪⢨⢊⠢⡈⡂⠢⠡⡑⡐⡐⠄⢅⠑⢕⢔⢐⠸⠸⡂⢌⢷⢫⠀⠌⡐⡐ 51 | ⠂⠌⡐⡘⠔⠤⢑⢑⠡⢁⢂⢂⡺⡱⡱⡱⡑⡐⡐⢅⠑⢌⠢⠡⠡⠡⡑⡐⢌⠢⠡⠡⡣⡣⡃⠢⡑⡐⠌⢌⢂⢂⠢⠨⡈⠢⡑⡐⢌⢢⡨⠰⢀⢂⢂⠂⠌⡐⡐⠠ 52 | ⠌⡐⡐⡐⠨⢈⢊⢐⠨⠐⡐⡌⡗⡕⡕⡕⡕⠔⢌⠢⡑⠄⠅⠅⢅⢑⠐⢌⠢⠡⠡⡑⢼⡘⢌⢂⠢⠨⡈⡂⡂⠢⠡⡑⢌⢂⠢⠨⠢⡑⡕⡅⡂⡂⠆⠌⡐⡐⠠⢁ 53 | ⡐⡐⡐⠠⢁⢂⢐⢐⠨⢐⢀⢳⢏⢎⢎⢎⢎⢊⠢⡑⠌⠌⠌⢌⢂⠢⡑⠄⠅⢅⢑⢌⢺⡘⡐⠄⢅⢑⢐⠐⠌⢌⠢⡈⡂⠢⠡⠡⡑⡕⡕⡕⡐⠠⠑⠅⡂⡂⠅⡂ 54 | ⡂⡂⡂⠅⠌⡐⡐⡐⠨⢐⠐⢜⡼⡸⡸⡸⡘⡔⢕⠌⠌⠌⢌⢂⠢⡑⠌⠌⢌⢂⢎⢂⢯⡊⡎⢌⢂⢂⠢⠡⡑⢄⢑⠐⠌⠌⢌⢌⢎⢎⡺⡘⡔⠨⢈⢂⢂⠂⠅⡂ 55 | ⢂⢂⠂⠅⢅⢢⢂⠂⠅⡂⠌⢆⢣⢣⢣⢣⢣⠣⡣⡣⡑⡅⢕⢐⢑⠌⢌⢌⢂⢎⢎⢢⡻⡢⢣⠣⡂⡢⠡⡑⢌⢂⠢⡡⡑⡕⡕⡕⡕⡕⡕⡕⠌⠌⡐⡐⡐⠨⢐⠠ 56 | ⡐⡐⠨⠨⢸⢐⠢⠨⢐⠠⠡⠈⢎⢣⡣⡣⡱⡱⡨⡪⡪⡪⡢⡑⡕⡕⡕⡕⡕⡕⡕⡕⡧⣣⢣⡣⡣⡪⡪⡪⡢⡣⡣⡪⡪⡪⡪⡪⡪⡪⡪⡪⠨⢐⠰⠰⠠⢁⠂⠌ 57 | ⡐⠠⠡⠡⢸⠐⣼⣇⠂⠌⠌⠌⠄⠣⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡪⡺⡸⣝⢜⢆⢇⢕⢕⢌⠪⡨⡊⡪⡨⡨⡊⡪⠪⡪⠪⠨⠠⢁⢢⠡⠡⢁⠂⠌⠌ 58 | ⠠⠡⠡⠡⠨⢔⣿⣿⣧⡡⠡⠡⠡⢁⠂⠅⡃⠇⢎⠎⢎⢌⢎⢎⢎⢪⠪⠪⡪⣪⢝⡜⠮⣪⢇⢇⢕⢑⢕⢕⢌⢪⠨⡪⡪⡪⡪⡪⠊⠌⠌⠨⢰⣟⡇⡃⡂⠌⠌⠌ 59 | ⠅⠅⠅⠅⠅⠸⣿⣿⣟⣿⣮⠨⠨⢐⠨⢐⠠⢁⠂⠅⠅⠅⠍⠍⠌⠄⠅⠅⠌⡐⡐⠠⢁⢂⠢⠨⢐⢐⠂⠅⠕⠕⠍⡊⡂⡂⠅⡂⠅⠅⠅⢅⣿⣿⠕⡐⠠⠡⠡⢁ 60 | ⠄⠅⢅⠡⠡⠡⠘⢿⣿⣿⣿⣷⣅⠂⠌⡐⠨⢐⠨⠨⠨⠨⠨⠨⠨⠨⠨⠠⡁⡂⡂⠅⡂⡂⠌⠌⡐⡐⠨⠨⠨⠨⢐⢐⢐⠠⢁⠂⠅⢅⣱⡿⣿⢓⢁⠂⠅⠅⡁⡂ 61 | ⠡⢁⠂⠌⠌⠌⢌⠠⢑⠫⠟⣿⢿⣿⣶⣤⣁⠂⠌⠌⠌⠌⠌⠌⠌⠌⠌⡐⡉⠢⠢⠕⠌⠄⠅⠅⡑⠄⡅⠅⠅⠅⡂⡂⡂⠌⡐⣨⣬⣾⡿⠏⡃⡂⡂⠌⠌⡐⢐⢐ 62 | ⠨⢐⠨⠨⠨⠨⢐⠨⢐⠨⢐⠠⠙⠽⣿⣿⡇⠅⠅⠕⠡⢣⠱⢅⢹⢈⢂⠢⠨⠨⠨⠨⠨⠨⡈⡂⠢⡑⡜⡑⢕⢃⢒⢒⢂⢑⣿⢿⢛⠁⡂⠅⡂⡂⡂⡂⡁⡂⡐⡐ 63 | ⡁⠢⠨⠨⠨⢐⢐⢌⢆⢅⠣⡡⡡⡑⡸⣿⡧⠡⡡⢡⢑⢂⢬⠸⡈⡂⠢⠡⠡⠡⡡⠡⡑⡑⡐⠌⢌⠢⡁⡢⡂⠆⠆⡂⡂⣢⡿⡑⠄⢕⠐⢕⢐⢔⢐⢐⢐⢐⢐⠐ 64 | ⠄⠅⠅⠅⠅⡆⡇⡇⡇⡇⡇⡢⡂⡂⡪⣻⠡⢁⠂⠆⠌⡂⡌⢕⢐⢌⠌⢌⢊⠪⣨⢂⢂⢂⠪⠨⠢⡑⢔⢐⡠⡡⡱⡨⡐⢸⠗⠌⢌⠢⡡⡡⡑⠕⡕⢕⢕⢔⢐⢈ 65 | ⠪⠨⢘⢈⠢⢉⠪⠪⡪⠎⡮⠮⢬⠪⢩⠪⠐⢠⠫⠣⢉⠢⠱⡐⢔⢐⢑⢑⢘⢌⢢⢱⠰⡐⡅⢅⢑⢌⠂⡆⡂⡑⡘⠄⠅⢸⠑⢅⠅⢕⢔⢕⢌⠪⡪⡢⡣⡣⢂⢂ 66 | ⠨⠨⢐⢐⢐⢐⠨⢐⢈⠢⠨⡈⠖⠨⢘⠨⠨⡸⠌⢌⠢⢩⠡⠩⠠⢑⢐⢂⢃⠂⠅⡃⠣⠆⢕⢕⢕⡢⢙⢐⢐⠐⠌⠪⢨⠠⠡⠁⠇⠧⡪⠢⠱⢱⢸⢸⣘⢌⢐⢐ 67 | ⠨⠨⢐⢐⢐⢐⠨⢐⠠⠡⡑⢌⠘⢌⠬⡨⢌⠪⣈⠢⡈⡊⠌⠌⠌⡐⡐⡐⡐⠨⠐⢄⡱⡝⡜⡬⠣⠨⠠⢑⠢⠡⢅⡑⡌⡊⠬⠨⡈⠢⠨⠨⡈⡂⡢⡂⡂⡐⡐⡘ 68 | ⠡⢁⢂⢂⢂⠂⠌⡐⠨⢐⢈⠢⢑⠄⢍⠘⠌⢅⢂⢂⢂⠢⠡⠡⡁⡂⡂⠢⠨⠨⠨⣰⢣⡣⡋⠌⠌⠌⢌⠢⠨⠨⡐⡐⡐⠌⢌⠐⠌⠌⢌⢂⠢⢂⢂⢂⢐⢐⢐⢐`; 69 | 70 | class NHENTAI_RULES { 71 | delay = 250; 72 | 73 | IS_VIDEO_PAGE = /^\/g\/\d+/.test(location.pathname); 74 | IS_SEARCH_PAGE = /^\/search\//.test(location.pathname); 75 | 76 | paginationOffset = parseInt(new URLSearchParams(location.search).searchParams.get('page')) || 1; 77 | paginationElement = document.querySelector('.pagination'); 78 | 79 | paginationLast = parseInt(document.querySelector('.pagination .last')?.href.match(/\d+/)[0]) || 1; 80 | CONTAINER = Array.from(document.querySelectorAll('.index-container, .container')).pop(); 81 | 82 | THUMB_URL(thumb) { 83 | return thumb.querySelector('.cover').href; 84 | } 85 | 86 | GET_THUMBS(html) { 87 | return html.querySelectorAll('.gallery'); 88 | } 89 | 90 | THUMB_IMG_DATA(thumb) { 91 | const img = thumb.querySelector('.cover img'); 92 | let imgSrc = img.getAttribute('data-src') || img.getAttribute('src'); 93 | if (!this.IS_VIDEO_PAGE) imgSrc = imgSrc?.replace('t5', 't3'); 94 | img.classList.remove('lazyload'); 95 | if (img.complete && img.getAttribute('src') && !img.src.includes('data:image')) { 96 | return {}; 97 | } 98 | return { img, imgSrc }; 99 | } 100 | 101 | THUMB_DATA(thumb) { 102 | const title = sanitizeStr(thumb.querySelector('.caption').innerText); 103 | const duration = 0; 104 | return { title, duration }; 105 | } 106 | 107 | paginationUrlGenerator = (offset) => { 108 | const url = new URL(window.location.href); 109 | url.searchParams.set('page', offset); 110 | return url.href; 111 | }; 112 | } 113 | 114 | class _3HENTAI_RULES { 115 | delay = 250; 116 | 117 | IS_VIDEO_PAGE = /^\/d\/\d+/.test(location.pathname); 118 | IS_SEARCH_PAGE = /^\/search/.test(location.pathname); 119 | 120 | paginationElement = document.querySelector('.pagination'); 121 | paginationLast = Math.max( 122 | ...Array.from(document.querySelectorAll('.pagination .page-link') || [], (e) => 123 | parseInt(e.innerText), 124 | ).filter(Number), 1); 125 | 126 | CONTAINER = [...document.querySelectorAll('.listing-container')].pop(); 127 | 128 | constructor() { 129 | Object.assign(this, this.URL_DATA()); 130 | } 131 | 132 | THUMB_URL(thumb) { 133 | return thumb.querySelector('a').href; 134 | } 135 | 136 | GET_THUMBS(html) { 137 | return html.querySelectorAll('.doujin-col'); 138 | } 139 | 140 | THUMB_IMG_DATA(thumb) { 141 | const img = thumb.querySelector('img'); 142 | let imgSrc = img.getAttribute('data-src') || img.getAttribute('src'); 143 | if (!this.IS_VIDEO_PAGE) imgSrc = imgSrc?.replace('t5', 't3'); 144 | img.classList.remove('lazyload'); 145 | if (img.complete && img.getAttribute('src') && !img.src.includes('data:image')) { 146 | return {}; 147 | } 148 | return { img, imgSrc }; 149 | } 150 | 151 | THUMB_DATA(thumb) { 152 | const title = sanitizeStr(thumb.querySelector('.title').innerText); 153 | const duration = 0; 154 | return { title, duration }; 155 | } 156 | 157 | URL_DATA() { 158 | const url = new URL(window.location.href); 159 | 160 | let paginationOffset = parseInt(url.searchParams.get('page')) || 1; 161 | let paginationUrlGenerator = (n) => { 162 | url.searchParams.set('page', n); 163 | return url.href; 164 | }; 165 | 166 | if (!this.IS_SEARCH_PAGE) { 167 | if (url.pathname === '/') url.pathname = '/1'; 168 | paginationOffset = parseInt(url.pathname.match(/\d+$/)) || 1; 169 | paginationUrlGenerator = (n) => { 170 | if (/\d+$/.test(url.pathname)) { 171 | url.pathname = url.pathname.replace(/\d+$/, n); 172 | } else { 173 | url.pathname = `${url.pathname}/${n}`; 174 | } 175 | return url.href; 176 | }; 177 | } 178 | 179 | return { paginationOffset, paginationUrlGenerator }; 180 | } 181 | } 182 | 183 | class EHENTAI_RULES { 184 | delay = 250; 185 | 186 | IS_VIDEO_PAGE = /^\/g\/\d+/.test(location.pathname); 187 | IS_SEARCH_PAGE = /f_search/.test(location.search) || /^\/tag\//.test(location.pathname); 188 | 189 | paginationElement = [...document.querySelectorAll('.searchnav')].pop(); 190 | paginationLast = 9999; 191 | paginationOffset = 1; 192 | CONTAINER = [...document.querySelectorAll('.itg.gld')].pop(); 193 | 194 | constructor() { 195 | if (this.IS_SEARCH_PAGE) { 196 | this.setThumbnailMode(); 197 | } 198 | this.eHentaiNext(); 199 | } 200 | 201 | paginationUrlGenerator = () => { 202 | this.eHentaiNext(); 203 | return unsafeWindow.PAGINATION_NEXT_; 204 | }; 205 | 206 | setThumbnailMode() { 207 | const selectInputT = document.querySelector('option[value=t]'); 208 | if (selectInputT) { 209 | const select = selectInputT.parentElement; 210 | if (select.value === 't') return; 211 | select.value = 't'; 212 | select.dispatchEvent(new Event('change')); 213 | } 214 | } 215 | 216 | THUMB_URL(thumb) { 217 | return thumb.querySelector('a').href; 218 | } 219 | 220 | GET_THUMBS(html) { 221 | return html.querySelectorAll('.gl1t'); 222 | } 223 | 224 | THUMB_IMG_DATA(thumb) { 225 | const img = thumb.querySelector('img'); 226 | const imgSrc = img.getAttribute('data-lazy-load') || img.getAttribute('src'); 227 | if (!img.getAttribute('data-lazy-load')) return {}; 228 | if (img.complete && img.getAttribute('src') && !img.src.includes('data:image')) { 229 | return {}; 230 | } 231 | return { img, imgSrc }; 232 | } 233 | 234 | THUMB_DATA(thumb) { 235 | const title = sanitizeStr(thumb.querySelector('.glname').innerText); 236 | const duration = 0; 237 | return { title, duration }; 238 | } 239 | 240 | eHentaiNext = async () => { 241 | if (!unsafeWindow.PAGINATION_NEXT_) { 242 | unsafeWindow.PAGINATION_NEXT_ = [...document.querySelectorAll('a#dnext[href]')].pop().href; 243 | } 244 | const doc = await bhutils.fetchHtml(unsafeWindow.PAGINATION_NEXT_); 245 | unsafeWindow.PAGINATION_NEXT_ = [...doc.querySelectorAll('a#dnext[href]')].pop().href; 246 | }; 247 | } 248 | 249 | const isNHENTAI = window.location.href.includes('nhentai'); 250 | const is3HENTAI = window.location.href.includes('3hentai'); 251 | const isEHENTAI = window.location.href.includes('e-henta'); 252 | 253 | let RULES; 254 | if (is3HENTAI) RULES = new _3HENTAI_RULES(); 255 | if (isNHENTAI) RULES = new NHENTAI_RULES(); 256 | if (isEHENTAI) RULES = new EHENTAI_RULES(); 257 | 258 | //==================================================================================================== 259 | 260 | const filterDescriptors = { 261 | english: { query: 'english', name: '🇬🇧' }, 262 | japanese: { query: 'japanese', name: '🇯🇵' }, 263 | chinese: { query: 'chinese', name: '🇨🇳' }, 264 | gay: { query: '-gay', name: 'Exclude Gay' }, 265 | fullColor: { query: 'color', name: 'Full Color' }, 266 | }; 267 | 268 | function checkURL(url_) { 269 | return Object.keys(filterDescriptors).reduce((url, k) => { 270 | const q = filterDescriptors[k].query; 271 | return state.custom[k] 272 | ? url.includes(q) 273 | ? url 274 | : `${url}+${q}` 275 | : url.replace(`+${q}`, () => ''); 276 | }, url_); 277 | } 278 | 279 | function filtersUI(state) { 280 | const btnContainer = Array.from(document.querySelectorAll('.sort-type')).pop(); 281 | const descs = Array.from(Object.keys(filterDescriptors)); 282 | [descs.slice(0, 3), [descs[3]], [descs[4]]].forEach((groupOfButtons) => { 283 | const btns = parseDom(`
    `); 284 | groupOfButtons.forEach((k) => { 285 | const btn = parseDom( 286 | `${filterDescriptors[k].name}`, 287 | ); 288 | btn.addEventListener('click', (e) => { 289 | e.preventDefault(); 290 | state.custom[k] = !state.custom[k]; 291 | window.location.href = checkURL(window.location.href); 292 | }); 293 | btns.append(btn); 294 | }); 295 | btnContainer?.after(btns); 296 | }); 297 | const fixedURL = checkURL(window.location.href); 298 | if (window.location.href !== fixedURL) window.location.href = checkURL(window.location.href); 299 | } 300 | 301 | function findSimilar(state) { 302 | let tags = Array.from(document.querySelectorAll('.tags .tag[href^="/tag/"] .name')) 303 | .map((tag) => tag.innerText) 304 | .join(' ') 305 | .split(' '); 306 | tags = Array.from(new Set(tags)).sort((a, b) => a.length < b.length); 307 | 308 | const urls = { 309 | searchSimilar: `/search/?q=${tags.slice(0, 5).join('+')}`, 310 | searchSimilarLess: `/search/?q=${tags.reverse().slice(0, 5).join('+')}`, 311 | }; 312 | 313 | Object.keys(urls).forEach((url) => { 314 | urls[url] = checkURL(urls[url]); 315 | }); 316 | 317 | Array.from(document.links) 318 | .filter((l) => /\/(search|category|tag|character|artist|group|parody)\/\w+/.test(l.href)) 319 | .forEach((l) => { 320 | l.href = checkURL( 321 | l.href 322 | .replace(/(search|category|tag|character|artist|group|parody)\//, 'search/?q=') 323 | .replace(/\/$/, ''), 324 | ); 325 | }); 326 | 327 | document 328 | .querySelector('.buttons') 329 | .append( 330 | parseDom( 331 | ` Similar`, 332 | ), 333 | parseDom( 334 | ` Less Similar`, 335 | ), 336 | ); 337 | } 338 | 339 | //==================================================================================================== 340 | 341 | function route() { 342 | if (!state.custom && isNHENTAI) { 343 | const custom = Object.entries(filterDescriptors).reduce((acc, [k, _]) => { 344 | acc[k] = false; 345 | return acc; 346 | }, {}); 347 | Object.assign(state, { custom }); 348 | } 349 | 350 | if (RULES.IS_VIDEO_PAGE) { 351 | if (isNHENTAI) findSimilar(state); 352 | } 353 | 354 | if (RULES.CONTAINER) { 355 | handleLoadedHTML(RULES.CONTAINER); 356 | } 357 | 358 | if (RULES.IS_SEARCH_PAGE && isNHENTAI) { 359 | filtersUI(state); 360 | } 361 | 362 | if (RULES.paginationElement) { 363 | createInfiniteScroller(store, handleLoadedHTML, RULES); 364 | } 365 | 366 | delete DefaultScheme.durationFilter; 367 | new JabroniOutfitUI(store, DefaultScheme); 368 | } 369 | 370 | //==================================================================================================== 371 | 372 | console.log(LOGO); 373 | 374 | const store = new JabroniOutfitStore(defaultStateInclExclMiscPagination); 375 | const { state, stateLocale } = store; 376 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 377 | store.subscribe(applyFilters); 378 | 379 | route(); 380 | -------------------------------------------------------------------------------- /pornhub-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name PornHub Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 2.3.0 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.pornhub.com/* 10 | // @exclude https://*.pornhub.com/embed/* 11 | // @icon https://www.google.com/s2/favicons?sz=64&domain=pornhub.com 12 | // @grant GM_addStyle 13 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 15 | // @run-at document-idle 16 | // @downloadURL https://update.sleazyfork.org/scripts/494001/PornHub%20Improved.user.js 17 | // @updateURL https://update.sleazyfork.org/scripts/494001/PornHub%20Improved.meta.js 18 | // ==/UserScript== 19 | 20 | const { watchElementChildrenCount, getAllUniqueParents, timeToSeconds, sanitizeStr, findNextSibling, DataManager, createInfiniteScroller } = window.bhutils; 21 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 22 | 23 | const LOGO = ` 24 | ⣞⣞⣞⣞⣞⣞⣞⣞⢞⣖⢔⠌⢆⠇⡇⢇⡓⡆⢠⠰⠠⡄⡄⡠⡀⡄⣄⠢⡂⡆⢆⢆⢒⢔⢕⢜⢔⢕⢕⢗⣗⢗⢗⢕⠕⠕⢔⢲⣲⢮⣺⣺⡺⡸⡪⡚⢎⢎⢺⢪ 25 | ⣺⣺⣺⣺⣺⣺⡺⣮⣳⣳⡳⣕⢥⠱⡘⢔⠱⠑⠡⣫⠈⡞⢼⠔⡑⡕⢕⢝⢜⢜⢜⢔⢅⢣⠪⡸⢸⠸⡸⡱⡑⢍⠢⡑⢌⢪⢨⢪⣺⡻⣗⣷⡿⣽⣺⡦⡵⣔⢷⢽ 26 | ⣺⣺⣺⣺⣺⣺⢽⣺⣺⣺⣺⣺⣪⢣⡑⡠⠐⠈⠨⡪⣂⠉⡪⡪⡆⢕⠅⡕⢕⢕⢕⢕⢕⢕⠱⡨⠢⡑⢌⢂⢊⢆⢕⠸⡨⢢⠱⡑⡜⡎⡞⡮⡯⡯⡯⡻⡽⡽⡽⣽ 27 | ⣺⣺⣞⣞⣞⡾⣽⣺⣺⣺⣺⣺⢮⡳⡽⣔⢔⢈⠐⠈⡎⡀⠅⢣⢫⡢⡑⠌⢜⠸⡸⡸⡱⠡⡱⢐⢅⠪⡐⡅⢕⠌⢆⠣⡊⢆⠣⡑⢌⠪⡊⡎⡎⡗⡝⡜⡜⢜⢹⢸ 28 | ⣞⣞⣾⣺⣳⢯⣗⣿⣺⣳⣗⡯⣗⡯⣟⣮⡳⣧⡳⣢⢢⠐⢰⣀⠂⡑⢕⠨⢂⠕⡑⢌⠢⠡⠠⡁⢂⢑⠨⠠⠡⡈⡂⡢⡂⡆⣆⡪⡨⡊⡂⠎⡢⡑⢕⢱⢘⢌⢎⢎ 29 | ⣳⣳⣳⣻⣺⢽⣺⣗⡿⣞⣷⣻⣗⣯⣗⣗⣟⡮⡯⣮⡳⡳⡔⡨⢆⠂⠱⡌⡐⠌⡂⠅⢅⠣⡑⢌⠢⡢⡑⡱⡡⡣⡣⡣⡳⣝⢮⣟⣿⣿⣿⣦⡈⢎⢎⢪⣪⢮⣳⣳ 30 | ⣺⣺⣳⣻⣞⣿⣺⢷⣟⣯⣿⣽⢿⣾⣳⣟⣮⢯⣟⢮⢯⢯⢮⢪⠪⡊⡈⢆⠢⢑⠨⠨⠢⡑⢌⠢⣃⢪⠨⡢⠱⡘⢜⢜⢜⢜⢗⣝⡯⡿⡿⣟⢦⣕⣞⣾⣺⣻⣞⣾ 31 | ⣺⣞⣷⣳⣟⣾⡽⣯⣟⣯⣿⣾⣿⣻⣽⣾⣽⣻⣺⢽⢽⢯⡯⣧⡣⡓⡔⠠⡑⠄⢕⠡⡑⢌⢢⢑⠔⡐⠅⠪⠨⢊⢢⢑⢕⢕⢕⢕⠱⡑⢕⢕⢗⣿⣺⣯⢿⣳⣟⢾ 32 | ⣞⣾⣺⣞⡷⣗⣿⣽⡾⣿⣟⣿⣟⣿⣿⣷⣿⣽⢾⡽⣽⢽⣽⣳⢽⡜⡌⢦⢘⢌⠢⡑⡌⢆⠕⡐⡁⠂⢅⠪⡘⢌⢪⠨⡢⡃⢎⠢⡑⢅⠕⢌⢎⣞⣞⢾⣝⣞⡮⡯ 33 | ⢷⣳⣗⡯⣟⡷⣿⢾⣻⣟⣿⣻⣿⣿⢿⣿⣾⣟⣿⢯⣯⣟⡾⣽⢯⣯⢯⣇⢇⢆⢣⠱⡘⡐⡁⡂⡐⠅⡅⢕⠸⡨⠢⡑⡰⢡⠡⡊⢌⠢⡑⡑⢜⢜⢮⠳⢓⢣⢫⢮ 34 | ⢽⣺⢾⣽⣳⣟⣟⣿⣯⣿⣿⣿⣿⣿⣿⣿⣻⣿⣻⡿⣞⣷⣻⣽⣻⣞⣿⡾⡵⡱⡡⢑⠈⠄⠂⠄⠂⡑⠨⡂⡣⡊⡪⢂⢎⠢⡃⡪⠢⣑⢱⡨⡮⣺⣖⠈⡜⢜⢼⢽ 35 | ⢽⣺⢽⣞⣾⣳⡯⣷⢿⡷⣿⣿⣻⣿⣟⣿⣽⣿⣻⣟⣯⡿⣞⣷⣻⣞⣷⣻⡳⡑⡅⠅⠠⠁⠌⡠⠁⠄⢅⢐⢐⠌⢔⠡⡢⢱⠨⡊⡎⡎⡮⡪⡯⡳⠡⡪⡪⡪⣯⣿ 36 | ⢽⣺⢽⣺⣳⢯⡿⣽⣟⣿⣻⣿⡿⣿⡿⣿⣿⣻⣯⣿⢷⣻⡽⣞⣷⣳⣳⡳⡱⡐⠄⡁⡂⡁⢂⠂⢅⠕⢅⢊⠢⡑⢡⢑⢌⣂⡣⡑⡅⣇⢇⢧⢕⢜⢜⢜⢮⢯⣿⣺ 37 | ⡻⡮⣟⣾⣺⢽⢯⣗⣿⣺⣽⡷⣿⣿⢿⣿⣯⣿⣯⣿⢿⣽⣻⡽⣞⣞⡞⡎⡎⠄⢂⠐⡀⡂⢅⠪⡐⡅⠇⠅⢂⠪⡐⠅⢕⠰⡑⡝⡜⡜⡎⡎⡎⡎⡎⢎⢎⢗⣗⢷ 38 | ⢯⣻⣺⣺⣺⢽⢯⢷⣻⣞⣷⢿⣻⣾⢿⣯⣿⡷⣿⣻⣯⡷⣯⣟⣷⡳⣝⢜⢜⠀⡂⠌⡐⢌⢢⢱⠨⡪⠨⠨⠀⠅⠊⢌⠢⡑⡑⢌⠎⡪⢊⢎⢊⠆⡊⡢⢂⢣⢣⣳ 39 | ⣳⣳⣳⡳⡽⡽⡯⣟⣾⣺⢽⡯⣷⣟⣿⣳⣿⢽⣯⢿⡷⣟⣷⣻⢾⣝⢮⡪⡪⡀⡂⠌⡌⡪⠢⡃⡇⡕⢕⠅⡅⢅⢑⠄⠅⡊⢌⠢⡑⢌⢢⢱⡰⣕⠒⡀⡊⡆⣗⢷ 40 | ⣺⡺⡮⡯⡯⡯⡯⣗⡷⣯⢯⡯⣗⣯⢷⣻⣞⣯⣟⣯⣟⣯⣿⢽⣻⣺⢵⢝⢮⢢⢂⢑⢌⠪⡪⢸⢨⢪⢪⢪⢪⢪⢢⢪⢪⢢⢱⢸⡸⣜⢮⢳⢱⡁⡜⣌⢆⢗⢕⢽ 41 | ⢞⢮⢯⢯⢯⢯⢯⣗⡯⣯⢯⣟⣷⣻⡯⣟⡷⣟⣷⢯⡿⣾⡽⣯⡿⣾⢽⢽⡱⡱⡱⢐⠔⡑⠜⡌⡎⡎⡮⡪⡣⣏⢮⡣⡳⣕⢕⢧⢯⡪⠣⡣⢃⢇⢣⢣⢳⣕⡯⣯ 42 | ⢝⣝⢽⢝⢽⣝⣗⣗⢯⢯⣟⢾⣺⣳⢯⡯⡿⣽⣞⣯⢿⣳⡿⣽⢯⢿⣽⣳⢝⡎⢌⠢⡑⢌⢪⠸⡸⡸⡸⡜⡮⣪⡳⣝⣕⣗⢽⢽⡕⢌⢪⢰⡱⡴⡵⡽⡽⣮⣻⡺ 43 | ⡳⡵⡹⡽⣕⣗⢷⢽⣝⣗⡯⣟⣞⡾⡽⡽⡯⣗⣗⡿⣽⣗⣿⢽⡯⣿⣞⡾⣝⡎⡢⢑⢌⢢⠱⡑⡕⡕⡕⡝⡜⡮⣺⣪⡺⡮⡯⣗⣿⢵⣳⣳⢯⢯⢯⢯⣻⣺⡪⡊ 44 | ⡯⡪⡯⣺⡺⡪⣯⣳⣳⡳⣯⣳⡳⣯⣻⢽⢯⣗⡷⡯⣗⡷⡯⡿⡽⣗⣯⢯⣗⣇⢊⠔⠔⡅⡣⡱⡸⡸⡸⡸⡸⡪⡺⣜⢮⢯⢯⡯⣿⢽⣺⣺⢽⢽⣝⣗⢗⠕⡁⡂ 45 | ⡯⡺⣪⢺⣪⣻⣺⡺⣮⣻⣺⡺⣽⣺⡺⡽⡽⡮⡯⡯⣗⡯⣿⢽⢯⣗⡯⣟⣞⣎⠐⢌⠪⡂⠑⠌⡆⢇⢇⢇⢇⢏⢮⡪⡫⣯⡳⡯⡯⣟⣞⢮⢯⣳⡳⡕⠅⠅⡀⢂ 46 | ⡮⡪⡪⡳⡵⣕⢗⣝⣞⣞⢮⣻⡺⡮⡯⡯⡯⣯⢯⣟⡾⣽⢽⡽⣽⡺⣽⣳⡳⡕⡈⡢⢑⠌⡄⢔⠸⡘⡜⡜⡜⡜⡜⣎⢯⢮⢯⢯⢯⡳⡽⣝⣗⢕⠇⠁⠀⠂⡂⠂`; 47 | 48 | class PORNHUB_RULES { 49 | delay = 350; 50 | 51 | IS_MODEL_PAGE = location.pathname.startsWith('/model/'); 52 | IS_VIDEO_PAGE = location.pathname.startsWith('/view_video.php'); 53 | IS_PLAYLIST_PAGE = location.pathname.startsWith('/playlist/'); 54 | 55 | paginationElement = document.querySelector('.paginationGated'); 56 | paginationOffset = parseInt(new URLSearchParams(location.search).get('page')) || 1; 57 | paginationLast = parseInt(document.querySelector('.page_next')?.previousElementSibling.innerText) || 1; 58 | 59 | CONTAINER = document.querySelector('ul.videos:not([id*=trailer]):not([class*=drop])'); 60 | intersectionObservable = this.CONTAINER && findNextSibling(this.CONTAINER); 61 | 62 | constructor() { 63 | if (this.paginationLast === 10) this.paginationLast = 999; 64 | } 65 | 66 | THUMB_URL(thumb) { 67 | return thumb.querySelector('.linkVideoThumb').href; 68 | } 69 | 70 | GET_THUMBS(html) { 71 | const parent = html.querySelector('ul.videos:not([id*=trailer]):not([class*=drop])') || html; 72 | return [...parent.querySelectorAll('li.videoBox.videoblock, li.videoblock')]; 73 | } 74 | 75 | THUMB_IMG_DATA(thumb) { 76 | const img = thumb.querySelector('.js-videoThumb.thumb.js-videoPreview'); 77 | const imgSrc = img?.getAttribute('data-mediumthumb') || img?.getAttribute('data-path').replace('{index}', '1'); 78 | if (!img?.complete || img.naturalWidth === 0) { return ({}); } 79 | return { img, imgSrc }; 80 | } 81 | 82 | THUMB_DATA(thumb) { 83 | const name = sanitizeStr(thumb.querySelector('.usernameWrap')?.innerText); 84 | const title = sanitizeStr(thumb.querySelector('span.title')?.innerText).concat(` user:${name}`); 85 | const duration = timeToSeconds(thumb.querySelector('.duration')?.innerText); 86 | return { title, duration }; 87 | } 88 | 89 | paginationUrlGenerator = (n) => { 90 | const url = new URL(location.href); 91 | url.searchParams.set('page', n); 92 | return url.href; 93 | } 94 | } 95 | 96 | const RULES = new PORNHUB_RULES(); 97 | 98 | //==================================================================================================== 99 | 100 | function route() { 101 | if (RULES.IS_VIDEO_PAGE) { 102 | const containers = getAllUniqueParents(document.querySelectorAll('li.js-pop.videoBox, li.js-pop.videoblock')).slice(2); 103 | containers.forEach(c => handleLoadedHTML(c, c)); 104 | } 105 | 106 | if (RULES.CONTAINER) { 107 | handleLoadedHTML(RULES.CONTAINER); 108 | } 109 | 110 | if (RULES.IS_PLAYLIST_PAGE) { 111 | handleLoadedHTML(RULES.CONTAINER); 112 | watchElementChildrenCount(RULES.CONTAINER, () => handleLoadedHTML(RULES.CONTAINER)); 113 | } 114 | 115 | if (RULES.paginationElement) { 116 | createInfiniteScroller(store, handleLoadedHTML, RULES); 117 | } 118 | 119 | new JabroniOutfitUI(store); 120 | } 121 | 122 | //==================================================================================================== 123 | 124 | console.log(LOGO); 125 | 126 | const store = new JabroniOutfitStore(defaultStateWithDuration); 127 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 128 | store.subscribe(applyFilters); 129 | 130 | route(); 131 | -------------------------------------------------------------------------------- /spankbang.com-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SpankBang.com Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 2.2.1 5 | // @license MIT 6 | // @description Infinite scroll (optional). Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.spankbang.com/* 10 | // @grant GM_addStyle 11 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 12 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 13 | // @run-at document-idle 14 | // @icon https://www.google.com/s2/favicons?sz=64&domain=spankbang.com 15 | // @downloadURL https://update.sleazyfork.org/scripts/493946/SpankBangcom%20Improved.user.js 16 | // @updateURL https://update.sleazyfork.org/scripts/493946/SpankBangcom%20Improved.meta.js 17 | // ==/UserScript== 18 | 19 | const { getAllUniqueParents, timeToSeconds, parseDom, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 20 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 21 | 22 | const LOGO = ` 23 | ⡕⡧⡳⡽⣿⣇⠀⢀⠀⠄⠐⡐⠌⡂⡂⠠⠀⠠⠀⠠⠀⠠⡐⡆⡇⣇⢎⢆⠆⠌⢯⡷⡥⡂⡐⠨⣻⣳⢽⢝⣵⡫⣗⢯⣺⢵⢹⡪⡳⣝⢮⡳⣿⣿⣿⣿⣿⣿⣿⣿ 24 | ⢎⢞⡜⣞⣿⡯⡆⠀⠄⠐⠀⢐⠡⢊⢐⢀⠈⠀⠄⠁⡀⠡⠸⡸⡪⣪⢺⢸⢱⠡⢑⡝⣟⣧⡂⠅⠪⣻⣎⢯⡺⣪⣗⢯⣺⢪⡣⡯⣝⢼⡪⣞⣽⣿⣿⣿⣿⣿⣿⣿ 25 | ⡱⡣⣣⢳⣻⣯⢿⡐⠀⠂⠁⢀⠪⢐⠐⠄⡀⠁⠄⠁⠀⠄⡑⠆⡎⢎⢎⢇⢕⠬⠀⢇⢣⢻⣞⠌⠌⡪⣷⡱⣝⢮⣺⢕⡗⣕⢧⢳⢕⢗⣝⢞⣾⣿⣿⣿⣿⣿⣿⣻ 26 | ⡸⡪⣪⡚⣞⣯⡗⣝⡀⠄⠁⡀⠌⡂⠅⡁⠄⠂⠐⠈⠀⢂⢐⢔⢜⢜⢜⢜⢔⢬⢊⠠⠨⠨⣻⣽⢐⠨⡺⣞⢼⢕⣗⡽⣪⢺⢜⢵⡹⣕⢵⡫⣾⣿⣿⣿⣿⣿⢯⣟ 27 | ⢎⢇⢇⢧⡫⣿⡞⡜⡄⠀⠄⠀⠐⠄⡁⠀⠄⠐⠀⠂⢁⢐⢔⢕⢕⠱⢱⠱⡱⡱⡱⡅⡂⢁⠪⡿⣎⡢⡘⢽⡪⡧⣳⡝⣜⢎⢗⢇⢯⡪⡧⣻⡺⣿⣿⣿⣿⢯⡻⡮ 28 | ⢕⢝⢜⡜⡮⣻⣗⢕⠅⡀⠂⠈⢈⠐⠀⠁⢀⠐⠀⡁⠄⢊⢢⢣⢣⢣⢣⢣⢣⢫⢪⢪⠀⠄⠨⡚⣿⣳⡜⡘⣗⢽⡺⡪⡺⡸⡵⡹⡕⡧⣫⡺⡺⣿⣿⡿⡯⡯⣫⢯ 29 | ⢸⢱⢱⢱⢕⢯⣿⢜⠌⢀⠀⠂⠀⠂⠁⠈⠀⠀⠄⠀⠄⢑⠌⡆⢇⢅⠕⡌⡬⡪⡪⡪⠀⠄⢁⠸⡸⣯⡻⣪⢺⡵⡫⡪⣣⢫⡪⡣⡏⣞⢜⢮⣫⣻⡿⣫⢯⢞⣗⢽ 30 | ⢪⢪⢪⢪⢎⢞⢾⡇⢕⠄⡆⡪⡘⡔⢔⢀⠐⠀⢀⠁⠠⠀⠑⠜⡌⡖⡕⡕⡕⢕⠑⠀⠠⠐⠀⡀⠕⡳⣻⣜⣕⣯⢣⠣⡣⡣⡣⡫⡪⡪⣎⢧⣧⡳⣝⢮⡳⡽⣜⢵ 31 | ⡸⡸⡸⡸⡸⡱⣫⢯⣲⢱⢱⢘⢜⢜⢕⢕⢅⣂⠀⡀⠐⠀⡁⢐⠨⢐⠡⡊⢌⠂⠄⠂⠀⠂⡠⡠⡢⡫⡳⡱⡝⡮⡪⣇⢧⢳⢕⣝⢮⡫⡮⡳⣕⢵⣓⢗⡝⡮⡺⡜ 32 | ⠱⡑⡕⡕⡝⡜⣎⢿⣪⢗⡝⡜⡜⡜⡜⢜⢕⢎⢎⢆⢎⢔⢄⠢⡌⢆⢕⠨⡢⢱⢰⢸⢸⣜⢮⢎⢎⢎⢇⢇⢯⢪⢇⡗⣝⢮⢳⢕⡗⣝⢮⡫⡮⣣⡳⡕⣝⢼⢸⢸ 33 | ⢜⢸⢨⢪⢪⢪⢪⣻⣺⢯⢎⣇⢧⢧⢧⢧⢯⢾⡵⣯⢾⣼⣜⣜⢜⢜⢜⢜⢜⢜⡜⡮⣗⡯⣟⣎⢎⢆⢇⢕⢕⢇⢗⡝⣜⢮⣳⣟⣾⣷⣷⣿⣮⣎⢎⢎⢎⢎⢎⠎ 34 | ⠣⡑⢌⠎⡜⢜⢜⢜⢜⡕⣧⡳⣫⢏⡯⡯⡯⡯⣯⢯⣟⢷⣻⣟⣿⢷⣷⣳⡽⡮⡾⣝⡎⡏⡎⡇⡕⡜⡔⡕⡕⣝⢜⢮⡳⡽⣺⢽⣻⢿⢿⣟⣿⣺⣝⢮⡢⡧⣧⡠ 35 | ⠐⠨⢢⢑⢅⢣⢱⢜⢞⢮⡳⣝⢮⣻⣪⢯⢯⡻⣺⢝⡾⣝⢷⢽⣺⢯⢿⢽⣻⣮⡳⣕⢇⢇⢇⢇⠪⡪⡪⡪⡪⣎⢯⢳⣹⡪⡯⣫⢯⣻⢽⣳⣳⣳⢳⣝⢮⡫⡷⣷ 36 | ⠀⠨⡂⡅⡖⡵⡹⣕⢏⡧⢯⢮⡳⣣⢷⢽⢕⣯⡳⣏⡯⣞⡽⣳⢽⢽⢽⣝⣗⣗⣟⢮⢳⡱⡱⡱⡑⡕⡜⢜⢜⡜⣎⢧⡣⡯⡺⣝⡵⡯⡯⣞⣞⢮⣳⡳⣝⣞⡽⣺ 37 | ⠀⠰⡸⡸⣸⢪⡫⣎⢗⢽⢕⣗⢽⣕⢯⣳⡫⣞⢮⣳⢽⣪⢯⢞⡽⣺⢵⣳⣳⡳⣳⢝⢵⡹⡪⣎⢎⢆⢇⢇⢇⢗⡕⡧⡳⣝⡝⡮⣞⡽⡽⡵⣳⣫⢞⡮⣗⣗⢽⣳ 38 | ⠀⠨⢢⢣⢳⡱⣝⢼⢭⢳⢳⢕⣗⢗⡽⣪⢞⡽⣕⢷⢝⡮⣏⡯⣞⣗⢽⡺⡼⣺⢵⡫⡧⣳⢹⢜⡜⡜⡜⢜⢪⢣⡫⣎⢯⡪⣞⣝⢞⢮⢏⡯⣳⣳⣫⢾⢕⣗⢯⣞ 39 | ⠀⠀⠱⡱⡱⡕⡧⡳⡕⣏⢗⣝⢮⢳⢝⢮⡳⣝⢮⡳⣝⢮⡳⣝⢮⡺⡵⣫⢯⡳⣳⢝⣞⡜⣎⢇⢎⠎⡪⢊⠎⡎⡎⡮⡺⣜⢮⢮⣫⣫⡳⡽⣕⣗⢵⣫⢯⡺⡵⣳ 40 | ⠀⠀⠈⢎⢎⠮⡺⡸⡕⡧⡳⣕⢝⢮⡫⣣⢯⣪⢳⢝⢮⢳⢝⢮⡳⣝⣝⢮⡳⣝⢮⡳⡵⣝⢼⢸⢐⠅⠂⡐⢅⢇⢣⢣⢳⡱⣝⠮⡮⣪⢞⣝⢮⡺⣕⢗⣗⢽⡹⣪ 41 | ⠀⠈⠀⠌⡒⡝⡜⣕⢕⢧⢫⡪⡳⡱⣝⢜⢮⡪⣳⢹⡪⣳⢹⢕⢽⡸⣜⢵⢝⢮⢳⢝⢞⢮⡪⡣⡣⡑⢅⢢⠱⡘⡜⡜⣜⢜⠮⡝⡮⡪⡧⡳⡵⣹⡪⡳⡕⡗⣝⢮ 42 | ⠀⠐⠀⡁⠌⢎⢎⢎⢮⢪⢎⢮⢪⢳⢱⢝⢜⢎⢮⢣⡫⡪⡎⣗⢕⢧⢳⡱⡝⣎⢗⣝⢕⡗⣝⢼⢸⢨⢢⢃⢇⢣⢣⢣⢣⢳⢹⢪⢎⢗⢝⡜⣎⢮⢪⡳⡹⣪⢺⢸ 43 | ⠀⠠⠐⢀⠐⠈⡎⡪⡪⡪⡪⡪⡣⡫⡪⡪⡣⡫⡪⡣⡣⡫⣪⢪⢺⢸⢪⢪⢺⢸⢪⡪⡺⡸⣪⢪⢪⢪⠸⡨⡊⡎⡎⡎⡇⡏⡎⡞⡜⡕⣕⢕⢕⡕⡇⡧⡫⡪⡪⡪ 44 | ⢀⠀⠐⠀⠄⠁⠨⡊⡎⡎⡎⡎⡎⡎⡎⡎⡇⡏⡎⡎⡎⡎⡎⡎⡎⡮⡪⡣⡳⡱⡱⡕⡝⣜⢜⢜⢜⢔⢕⢱⠸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡨⡣⡣⡣⡣⡣⡣⡃⡇ 45 | ⠀⠀⠈⡀⠂⠐⠀⢑⠜⡌⢎⢪⠪⡪⡪⡪⢪⠪⡪⢪⠪⡪⡪⢪⠪⡊⡎⡜⡌⡎⢎⢎⢎⢎⠎⡎⡪⠢⡑⢌⢪⢘⢔⠱⡡⢣⢃⢇⠕⡕⢅⢇⢣⢱⢑⢕⠸⡐⡱⡘`; 46 | 47 | class SPANKBANG_RULES { 48 | delay = 300; 49 | 50 | paginationElement = document.querySelector('.paginate-bar, .pagination'); 51 | 52 | paginationLast = parseInt( 53 | document.querySelector('.paginate-bar .status span')?.innerText.match(/\d+/)?.[0] || 54 | Array.from(document.querySelectorAll('.pagination a'))?.at(-2)?.innerText, 55 | ); 56 | 57 | CONTAINER = document.querySelectorAll('.main-container .js-media-list, .main_content_container .video-list')[0]; 58 | HAS_VIDEOS = !!this.GET_THUMBS(document.body).length > 0; 59 | 60 | constructor() { 61 | Object.assign(this, this.URL_DATA()); 62 | } 63 | 64 | GET_THUMBS(html) { 65 | return Array.from(html.querySelectorAll('.video-item:not(.clear-fix), .js-video-item') || []); 66 | } 67 | 68 | THUMB_URL(thumb) { 69 | return thumb.querySelector('a[title]').href; 70 | } 71 | 72 | THUMB_IMG_DATA(thumb) { 73 | const img = thumb.querySelector('img'); 74 | const imgSrc = img.getAttribute('data-src'); 75 | img.removeAttribute('data-src'); 76 | return { img, imgSrc }; 77 | } 78 | 79 | THUMB_DATA(thumb) { 80 | const title = sanitizeStr(thumb.querySelector('[title]')?.getAttribute('title')); 81 | const duration = timeToSeconds(thumb.querySelector('span.l')?.innerText); 82 | return { title, duration }; 83 | } 84 | 85 | URL_DATA() { 86 | const url = new URL(window.location.href); 87 | const paginationOffset = parseInt(url.pathname.match(/\/(\d+)\/?$/)?.pop()) || 1; 88 | if (!/\/\d+\/$/.test(url.pathname)) url.pathname = `${url.pathname}/${paginationOffset}/`; 89 | 90 | const paginationUrlGenerator = (n) => { 91 | url.pathname = url.pathname.replace(/\/\d+\/$/, `/${n}/`); 92 | return url.href; 93 | }; 94 | 95 | return { paginationOffset, paginationUrlGenerator }; 96 | } 97 | } 98 | 99 | const RULES = new SPANKBANG_RULES(); 100 | 101 | //==================================================================================================== 102 | 103 | function route() { 104 | if (RULES.HAS_VIDEOS) { 105 | new JabroniOutfitUI(store); 106 | getAllUniqueParents(RULES.GET_THUMBS(document.body)).forEach((c) => handleLoadedHTML(c, c)); 107 | setTimeout(() => getAllUniqueParents(RULES.GET_THUMBS(document.body)).forEach((c) => handleLoadedHTML(c, c)), 100); 108 | } 109 | 110 | if (RULES.paginationElement) { 111 | createInfiniteScroller(store, handleLoadedHTML, RULES); 112 | } 113 | } 114 | 115 | //==================================================================================================== 116 | 117 | console.log(LOGO); 118 | 119 | const store = new JabroniOutfitStore(defaultStateWithDuration); 120 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 121 | store.subscribe(applyFilters); 122 | 123 | route(); 124 | -------------------------------------------------------------------------------- /thisvid.com-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ThisVid.com Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 5.2.4 5 | // @license MIT 6 | // @description Infinite scroll (optional). Preview for private videos. Filter: duration, public/private, include/exclude terms. Check access to private vids. Mass friend request button. Sorts messages. Download button 📼 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.thisvid.com/* 10 | // @icon https://www.google.com/s2/favicons?sz=64&domain=thisvid.com 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @require https://cdn.jsdelivr.net/npm/lskdb@1.0.2/dist/lskdb.umd.js 15 | // @run-at document-idle 16 | // @downloadURL https://update.sleazyfork.org/scripts/485716/ThisVidcom%20Improved.user.js 17 | // @updateURL https://update.sleazyfork.org/scripts/485716/ThisVidcom%20Improved.meta.js 18 | // ==/UserScript== 19 | /* globals $ */ 20 | 21 | const { 22 | Tick, 23 | parseDom, 24 | fetchWith, 25 | fetchHtml, 26 | timeToSeconds, 27 | parseCSSUrl, 28 | circularShift, 29 | range, 30 | listenEvents, 31 | replaceElementTag, 32 | sanitizeStr, 33 | chunks, 34 | downloader, 35 | AsyncPool, 36 | computeAsyncOneAtTime, 37 | InfiniteScroller, 38 | createInfiniteScroller, 39 | DataManager, 40 | } = window.bhutils; 41 | Object.assign(unsafeWindow, { bhutils: window.bhutils }); 42 | const { 43 | JabroniOutfitStore, 44 | defaultStateWithDurationAndPrivacy, 45 | JabroniOutfitUI, 46 | defaultSchemeWithPrivateFilter, 47 | } = window.jabronioutfit; 48 | const { LSKDB } = window.lskdb; 49 | 50 | const SponsaaLogo = ` 51 | Kono bangumi ha(wa) goran no suponsaa no teikyou de okurishimasu⣿⣿⣿⣿ 52 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 53 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⡟⣟⢻⢛⢟⠿⢿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 54 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣾⣾⣵⣧⣷⢽⢮⢧⢿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 55 | ⣿⣿⣿⣿⣿⣿⣯⣭⣧⣯⣮⣧⣯⣧⣯⡮⣵⣱⢕⣕⢕⣕⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 56 | ⣿⣿⣿⣿⣿⡫⡻⣝⢯⡻⣝⡟⣟⢽⡫⡟⣏⢏⡏⡝⡭⡹⡩⣻⣿⣿⣿⣿⠟⠟⢟⡟⠟⠻⠛⠟⠻⠻⣿⣿⣿⡟⠟⠻⠛⠟⠻⠻⣿⣿ 57 | ⣿⣿⣿⣿⡿⣻⣿⣿⣿⡿⣿⣿⡿⣿⣿⢿⢿⡻⢾⠽⡺⡞⣗⠷⣿⣿⣿⡏⠀⠀⠀⣣⣤⡄⠀⠠⣄⡆⠫⠋⠻⢕⣤⡄⠀ ⢀⣤⣔⣿⣿ 58 | ⣿⣿⣿⣿⣷⣷⣷⣾⣶⣯⣶⣶⣷⣷⣾⣷⣳⣵⣧⣳⡵⣕⣮⣞⣾⣿⡟⠄⢀⣦⠀⢘⣽⡇⠀⠨⣿⡌⠀⠣⣠⠹⠿⡭⠀ ⠐⣿⣿⣿⣿ 59 | ⣿⣿⣿⣿⣕⣵⣱⣫⣳⡯⣯⣫⣯⣞⣮⣎⣮⣪⣢⣣⣝⣜⡜⣜⣾⣿⠃⠀⠀⠑⠀⠀⢺⡇⠀ ⢘⣾⠀⢄⢄⠘⠀⢘⢎⠀⢈⣿⣿⣿⣿ 60 | ⣿⣿⣿⣿⣿⣙⣛⣛⢻⢛⢟⢟⣛⢻⢹⣙⢳⢹⢚⢕⣓⡓⡏⣗⣿⣓⣀⣀⣿⣿⣮⢀⣀⣇⣀⣐⣿⣔⣀⢁⢀⣀⣀⣅⣀⡠⣿⣿⣿⣿ 61 | ⣿⣿⣿⣿⣿⣾⡞⣞⢷⡻⡯⡷⣗⢯⢷⢞⢷⢻⢞⢷⡳⣻⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 62 | ⣿⣿⣿⣿⣿⣿⣷⣵⡵⣼⢼⢼⡴⣵⢵⡵⣵⢵⡵⣵⣪⣾⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 63 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣫⣪⡪⡣⣫⣪⣣⣯⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ 64 | ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿`; 65 | 66 | GM_addStyle(` 67 | .haveNoAccess { background: linear-gradient(to bottom, #b50000 0%, #2c2c2c 100%) red !important; } 68 | .haveAccess { background: linear-gradient(to bottom, #4e9299 0%, #2c2c2c 100%) green !important; } 69 | .success { background: linear-gradient(#2f6eb34f, #66666647) !important; } 70 | .failure { background: linear-gradient(rgba(179, 47, 47, 0.31), rgba(102, 102, 102, 0.28)) !important; } 71 | .friend-button { background: radial-gradient(#5ccbf4, #e1ccb1) !important; } 72 | .friendProfile { background: radial-gradient(circle, rgb(28, 42, 50) 48%, rgb(0, 0, 0) 100%) !important; } 73 | `); 74 | 75 | class THISVID_RULES { 76 | delay = 350; 77 | 78 | constructor() { 79 | const { href, pathname } = window.location; 80 | 81 | this._PAGINATION_ALLOWED = [ 82 | /\.com\/$/, 83 | /\/(categories|tags?)\//, 84 | /\/?q=.*/, 85 | /\/(\w+-)?(rated|popular|private|newest|winners|updates)\/(\d+\/)?$/, 86 | /\/members\/\d+\/\w+_videos\//, 87 | /\/playlist\/\d+\//, 88 | /\/my_(\w+)_videos\//, 89 | /\/my_wall\/#\w+/, 90 | ].some((r) => r.test(href)); 91 | 92 | this.IS_MEMBER_PAGE = /^\/members\/\d+\/$/.test(pathname); 93 | this.IS_WATCHLATER_KIND = /^\/my_(\w+)_videos\//.test(pathname); 94 | this.IS_MESSAGES_PAGE = /^\/my_messages\//.test(pathname); 95 | this.IS_PLAYLIST = /^\/playlist\/\d+\//.test(pathname); 96 | this.IS_VIDEO_PAGE = /^\/videos\//.test(pathname); 97 | 98 | this.PAGE_HAS_VIDEO = this.GET_THUMBS(document).length > 0; 99 | 100 | this.paginationElement = document.querySelector('.pagination'); 101 | this.paginationLast = this.getPaginationLast(); 102 | this.paginationOffset = this.getPaginationOffset(); 103 | this.paginationUrlGenerator = this.getPaginationGenerator(); 104 | 105 | this.CONTAINER = Array.from(document.querySelectorAll('.thumbs-items')).pop(); 106 | 107 | this.MY_ID = document.querySelector('[target="_self"]')?.href.match(/\/(\d+)\//)[1] || null; 108 | this.LOGGED_IN = !!this.MY_ID; 109 | this.IS_MY_MEMBER_PAGE = this.LOGGED_IN && !!document.querySelector('.my-avatar'); 110 | this.IS_OTHER_MEMBER_PAGE = !this.IS_MY_MEMBER_PAGE && this.IS_MEMBER_PAGE; 111 | this.IS_MEMBER_FRIEND = 112 | this.IS_OTHER_MEMBER_PAGE && 113 | document.querySelector('.case-left')?.innerText.includes('is in your friends'); 114 | 115 | if (this.IS_MEMBER_FRIEND) { 116 | document.querySelector('.profile').classList.add('friendProfile'); 117 | } 118 | 119 | if (this.IS_PLAYLIST) { 120 | const videoUrl = this.PLAYLIST_THUMB_URL(pathname); 121 | const desc = document.querySelector('.tools-left > li:nth-child(4) > .title-description'); 122 | const link = replaceElementTag(desc, 'a'); 123 | link.href = videoUrl; 124 | } 125 | } 126 | 127 | getPaginationOffset() { 128 | return this.IS_PLAYLIST ? 1 : parseInt(location.pathname.match(/\/(\d+)\/?$/)?.[1]) || 1; 129 | } 130 | 131 | getPaginationLast(doc) { 132 | const e = doc || document; 133 | return parseInt(e.querySelector('.pagination-next')?.previousElementSibling?.innerText) || 1; 134 | } 135 | 136 | GET_THUMBS(html) { 137 | if (this.IS_WATCHLATER_KIND) { 138 | return Array.from(html.querySelectorAll('.thumb-holder')); 139 | } 140 | let thumbs = Array.from(html.querySelectorAll('.tumbpu[title]')); 141 | if (thumbs.length === 0 && html?.classList?.contains('tumbpu')) thumbs = [html]; 142 | return thumbs.filter((thumb) => !thumb?.parentElement.classList.contains('thumbs-photo')); 143 | } 144 | 145 | PLAYLIST_THUMB_URL(src) { 146 | return src.replace(/playlist\/\d+\/video/, () => 'videos'); 147 | } 148 | 149 | THUMB_URL(thumb) { 150 | if (this.IS_WATCHLATER_KIND) { 151 | return thumb.firstElementChild.href; 152 | } 153 | let url = thumb.getAttribute('href'); 154 | if (this.IS_PLAYLIST) url = this.PLAYLIST_THUMB_URL(url); 155 | return url; 156 | } 157 | 158 | getPaginationGenerator(proxyLocation) { 159 | const url = new URL(proxyLocation || window.location); 160 | 161 | if (url.pathname === '/') url.pathname = '/latest-updates/'; 162 | if (!/\/(\d+)\/?$/.test(url.pathname)) 163 | url.pathname = `${url.pathname}${this.paginationOffset}/`; 164 | 165 | const paginationUrlGenerator = (n) => { 166 | if (this.IS_PLAYLIST) { 167 | url.search = `mode=async&function=get_block&block_id=playlist_view_playlist_view&sort_by=added2fav_date&from=${n}&_=${Date.now()}`; 168 | } else { 169 | url.pathname = url.pathname.replace(/\/\d+\/$/, `/${n}/`); 170 | } 171 | return url.href; 172 | }; 173 | 174 | return paginationUrlGenerator; 175 | } 176 | 177 | THUMB_IMG_DATA(thumb) { 178 | const img = thumb.querySelector('img'); 179 | const privateThumb = thumb.querySelector('.private'); 180 | let imgSrc = img?.getAttribute('data-original'); 181 | if (privateThumb) { 182 | imgSrc = parseCSSUrl(privateThumb.style.background); 183 | privateThumb.removeAttribute('style'); 184 | } 185 | const count = img.getAttribute('data-cnt'); 186 | if (!count || count === '6') img.removeAttribute('data-cnt'); 187 | img.classList.remove('lazy-load'); 188 | img.classList.add('tracking'); 189 | 190 | if (this.IS_PLAYLIST) { 191 | img.onmouseover = img.onmouseout = null; 192 | img.removeAttribute('onmouseover'); 193 | img.removeAttribute('onmouseout'); 194 | } 195 | 196 | return { img, imgSrc }; 197 | } 198 | 199 | THUMB_DATA(thumb) { 200 | const title = sanitizeStr(thumb.querySelector('.title').innerText); 201 | const duration = timeToSeconds(thumb.querySelector('.thumb > .duration').textContent); 202 | return { title, duration }; 203 | } 204 | 205 | IS_PRIVATE(thumb) { 206 | return !thumb.querySelector('.private'); 207 | } 208 | } 209 | 210 | const RULES = new THISVID_RULES(); 211 | 212 | //==================================================================================================== 213 | 214 | function friend(id, message = '') { 215 | return fetchWith(FRIEND_REQUEST_URL(id, message)); 216 | } 217 | 218 | const FRIEND_REQUEST_URL = (id, message = '') => 219 | `https://thisvid.com/members/${id}/?action=add_to_friends_complete&function=get_block&block_id=member_profile_view_view_profile&format=json&mode=async&message=${message}`; 220 | 221 | const USERS_PER_PAGE = 24; 222 | 223 | async function getMemberFriends(memberId, start, end) { 224 | const { friendsCount } = await getMemberData(memberId); 225 | const offset = Math.ceil(friendsCount / USERS_PER_PAGE); 226 | const pages = range(offset) 227 | .slice(start, end) 228 | .map((o) => `https://thisvid.com/members/${memberId}/friends/${o}/`); 229 | const pagesFetched = pages.map((p) => fetchHtml(p)); 230 | const friends = (await Promise.all(pagesFetched)).flatMap(getMembers); 231 | return friends; 232 | } 233 | 234 | function getMembers(el) { 235 | const friendsList = el.querySelector('#list_members_friends_items'); 236 | return Array.from(friendsList?.querySelectorAll('.tumbpu') || []) 237 | .map((e) => e.href.match(/\d+/)?.[0]) 238 | .filter((_) => _); 239 | } 240 | 241 | async function friendMemberFriends(orientationFilter) { 242 | const memberId = window.location.pathname.match(/\d+/)[0]; 243 | friend(memberId); 244 | const friends = await getMemberFriends(memberId); 245 | const spool = new AsyncPool(60); 246 | friends 247 | .map((fid) => { 248 | if (!orientationFilter) return () => friend(fid); 249 | return () => 250 | getMemberData(fid).then(async ({ orientation, uploadedPrivate }) => { 251 | if ( 252 | uploadedPrivate > 0 && 253 | (orientation === orientationFilter || 254 | (orientationFilter === 'Straight' && orientation === 'Lesbian')) 255 | ) { 256 | await friend(fid); 257 | } 258 | }); 259 | }) 260 | .forEach((f) => spool.push(f)); 261 | await spool.run(); 262 | } 263 | 264 | function initFriendship() { 265 | GM_addStyle( 266 | '.buttons {display: flex; flex-wrap: wrap} .buttons button, .buttons a {align-self: center; padding: 4px; margin: 5px;}', 267 | ); 268 | 269 | const buttonAll = parseDom( 270 | '', 271 | ); 272 | const buttonStraightOnly = parseDom( 273 | '', 274 | ); 275 | const buttonGayOnly = parseDom( 276 | '', 277 | ); 278 | const buttonBisexualOnly = parseDom( 279 | '', 280 | ); 281 | 282 | document 283 | .querySelector('.buttons') 284 | .append(buttonAll, buttonStraightOnly, buttonGayOnly, buttonBisexualOnly); 285 | 286 | buttonAll.addEventListener('click', (e) => handleClick(e), { once: true }); 287 | buttonStraightOnly.addEventListener('click', (e) => handleClick(e, 'Straight'), { once: true }); 288 | buttonGayOnly.addEventListener('click', (e) => handleClick(e, 'Gay'), { once: true }); 289 | buttonBisexualOnly.addEventListener('click', (e) => handleClick(e, 'Bisexual'), { once: true }); 290 | 291 | function handleClick(e, orientationFilter) { 292 | const button = e.target; 293 | button.style.background = 'radial-gradient(#ff6114, #5babc4)'; 294 | button.innerText = 'processing requests'; 295 | friendMemberFriends(orientationFilter).then(() => { 296 | button.style.background = 'radial-gradient(blue, lightgreen)'; 297 | button.innerText = 'friend requests sent'; 298 | }); 299 | } 300 | } 301 | 302 | //==================================================================================================== 303 | 304 | async function sendMessage(uid, message = 'add me pls') { 305 | const url = new URL( 306 | `https://thisvid.com/members/${uid}/?action=send_message_complete&function=get_block&block_id=member_profile_view_view_profile&format=json&mode=async`, 307 | ); 308 | url.searchParams.append('message', message); 309 | await fetch(url.href); 310 | } 311 | 312 | //==================================================================================================== 313 | 314 | async function getMemberData(id) { 315 | const url = id.includes('member') ? id : `/members/${id}/`; 316 | const doc = await fetchHtml(url); 317 | const data = {}; 318 | 319 | doc.querySelectorAll('.profile span').forEach((s) => { 320 | if (s.innerText.includes('Name:')) { 321 | data.name = s.firstElementChild.innerText.trim(); 322 | } 323 | if (s.innerText.includes('Orientation:')) { 324 | data.orientation = s.firstElementChild.innerText.trim(); 325 | } 326 | if (s.innerText.includes('Videos uploaded:')) { 327 | data.uploadedPublic = parseInt(s.children[0].innerText); 328 | data.uploadedPrivate = parseInt(s.children[1].innerText); 329 | } 330 | }); 331 | 332 | data.friendsCount = 333 | parseInt( 334 | doc.querySelector('#list_members_friends')?.firstElementChild.innerText.match(/\d+/g).pop(), 335 | ) || 0; 336 | 337 | return data; 338 | } 339 | 340 | //==================================================================================================== 341 | 342 | unsafeWindow.requestPrivateAccess = (e, memberid) => { 343 | e.preventDefault(); 344 | friend(memberid, ''); 345 | e.target.innerText = e.target.innerText.replace('🚑', '🍆'); 346 | }; 347 | 348 | async function checkPrivateVideoAccess(url) { 349 | const html = await fetchHtml(url); 350 | const holder = html.querySelector('.video-holder > p'); 351 | 352 | const access = !holder; 353 | 354 | const uploaderEl = holder ? holder.querySelector('a') : html.querySelector('a.author'); 355 | const uploaderURL = uploaderEl.href.match(/\d+/).at(-1); 356 | const uploaderName = uploaderEl.innerText; 357 | 358 | return { 359 | access, 360 | uploaderURL, 361 | uploaderName, 362 | }; 363 | } 364 | 365 | const uploadersNotInFriendlist = new Set(); 366 | 367 | async function requestAccess() { 368 | const checkAccess = async (thumb) => { 369 | const { access, uploaderURL } = await checkPrivateVideoAccess(thumb.href || thumb.querySelector('a').href); 370 | 371 | if (!access) { 372 | thumb.classList.add('haveNoAccess'); 373 | 374 | if (!uploadersNotInFriendlist.has(uploaderURL)) friend(uploaderURL); 375 | } else { 376 | thumb.classList.add('haveAccess'); 377 | } 378 | }; 379 | 380 | const f = []; 381 | document 382 | .querySelectorAll('.tumbpu:has(.private), .thumb-holder:has(.private)') 383 | .forEach((thumb) => { 384 | if (!thumb.classList.contains('haveNoAccess') && !thumb.classList.contains('haveAccess')) { 385 | f.push(() => checkAccess(thumb)); 386 | } 387 | }); 388 | computeAsyncOneAtTime(f); 389 | } 390 | 391 | //==================================================================================================== 392 | 393 | const createDownloadButton = () => 394 | downloader({ 395 | append: '', 396 | after: '.share_btn', 397 | button: '
  • 📼
  • ', 398 | cbBefore: () => $('.fp-ui').click(), 399 | }); 400 | 401 | //==================================================================================================== 402 | 403 | class PreviewAnimation { 404 | constructor(element, delay = 750) { 405 | $('img[alt!="Private"]').off(); 406 | this.tick = new Tick(delay); 407 | listenEvents(element, ['mouseover', 'touchstart'], this.animatePreview); 408 | } 409 | 410 | ITERATE_PREVIEW_IMG = (img) => { 411 | const count = parseInt(img.getAttribute('data-cnt')) || 6; 412 | img.src = img 413 | .getAttribute('src') 414 | .replace(/(\d+)(?=\.jpg$)/, (_, n) => `${circularShift(parseInt(n), count)}`); 415 | }; 416 | 417 | animatePreview = (e) => { 418 | const { target: el, type } = e; 419 | if (!el.classList.contains('tracking') || !el.getAttribute('src')) return; 420 | this.tick.stop(); 421 | if (type === 'mouseover' || type === 'touchstart') { 422 | const orig = el.getAttribute('src'); 423 | this.tick.start( 424 | () => this.ITERATE_PREVIEW_IMG(el), 425 | () => { 426 | el.src = orig; 427 | }, 428 | ); 429 | el.addEventListener( 430 | type === 'mouseover' ? 'mouseleave' : 'touchend', 431 | () => this.tick.stop(), 432 | { once: true }, 433 | ); 434 | } 435 | }; 436 | } 437 | 438 | //==================================================================================================== 439 | 440 | function highlightMessages() { 441 | for (const member of document.querySelectorAll('.user-avatar > a')) { 442 | getMemberData(member.href).then(({ uploadedPublic, uploadedPrivate }) => { 443 | if (uploadedPrivate > 0) { 444 | const success = !member.parentElement.nextElementSibling.innerText.includes('declined'); 445 | member.parentElement.parentElement.classList.add(success ? 'success' : 'failure'); 446 | } 447 | member.parentElement.parentElement.querySelector('.user-comment p').innerText += 448 | ` | videos: ${uploadedPublic} public, ${uploadedPrivate} private`; 449 | }); 450 | } 451 | } 452 | 453 | //==================================================================================================== 454 | 455 | const lskdb = new LSKDB(); 456 | 457 | async function getMemberVideos(id, type = 'private') { 458 | const { uploadedPrivate, uploadedPublic, name } = await getMemberData(id); 459 | const videosCount = type === 'private' ? uploadedPrivate : uploadedPublic; 460 | const paginationLast = Math.ceil(videosCount / 48); 461 | 462 | const iteratable_url = RULES.getPaginationGenerator( 463 | new URL(`https://thisvid.com/members/${id}/${type}_videos/`), 464 | ); 465 | const memberVideosGenerator = InfiniteScroller.createPaginationGenerator( 466 | 0, 467 | paginationLast, 468 | iteratable_url, 469 | ); 470 | return { name, videosCount, memberVideosGenerator }; 471 | } 472 | 473 | async function getMembersVideos(id, friendsCount, memberGeneratorCallback, type = 'private') { 474 | let skipFlag = false; 475 | let skipCount = 1; 476 | let minVideosCount = 1; 477 | 478 | const skipCurrentMember = (n = 1) => { 479 | skipFlag = true; 480 | skipCount = n; 481 | }; 482 | const filterVideosCount = (n = 1) => { 483 | minVideosCount = n; 484 | }; 485 | 486 | let membersIds = await getMemberFriends(id, 0, 1); 487 | getMemberFriends(id, 1).then((r) => { 488 | membersIds = membersIds.concat(r); 489 | }); 490 | 491 | async function* pageGenerator() { 492 | let currentGenerator = null; 493 | for (let c = 0; c < friendsCount - 1; c++) { 494 | if (lskdb.hasKey(membersIds[c])) continue; 495 | 496 | if (!currentGenerator) { 497 | const { memberVideosGenerator, name, videosCount } = await getMemberVideos( 498 | membersIds[c], 499 | type, 500 | ); 501 | 502 | if (memberVideosGenerator && videosCount >= minVideosCount) { 503 | currentGenerator = memberVideosGenerator; 504 | memberGeneratorCallback(name, videosCount, membersIds[c]); 505 | } else continue; 506 | } 507 | 508 | const { 509 | value: { url } = {}, 510 | done, 511 | } = await currentGenerator.next(); 512 | 513 | if (done || skipFlag) { 514 | c += skipCount - 1; 515 | skipCount = 1; 516 | currentGenerator = null; 517 | skipFlag = false; 518 | } else { 519 | yield { url, offset: c }; 520 | } 521 | } 522 | } 523 | 524 | return { 525 | pageGenerator: () => pageGenerator(membersIds, type), 526 | skipCurrentMember, 527 | filterVideosCount, 528 | }; 529 | } 530 | 531 | function createPrivateFeedButton() { 532 | const container = document.querySelectorAll('.sidebar ul')[1]; 533 | const buttonPrv = parseDom( 534 | `
  • My Friends Private Videos
  • `, 535 | ); 536 | const buttonPub = parseDom( 537 | `
  • My Friends Public Videos
  • `, 538 | ); 539 | container.append(buttonPub, buttonPrv); 540 | } 541 | 542 | async function createPrivateFeed() { 543 | createPrivateFeedButton(); 544 | if (!window.location.hash.includes('feed')) return; 545 | const isPubKey = window.location.hash === '#public_feed'; 546 | 547 | const container = parseDom('
    '); 548 | const ignored = parseDom('

    IGNORED:

    '); 549 | 550 | Object.assign(defaultSchemeWithPrivateFilter, { 551 | controlsSkip: [ 552 | { type: 'button', innerText: 'skip 10', callback: async () => skip(10) }, 553 | { type: 'button', innerText: 'skip 100', callback: async () => skip(100) }, 554 | { type: 'button', innerText: 'skip 1000', callback: async () => skip(1000) }, 555 | ], 556 | controlsFilter: [ 557 | { type: 'button', innerText: 'filter >10', callback: async () => filterVidsCount(10) }, 558 | { type: 'button', innerText: 'filter >25', callback: async () => filterVidsCount(25) }, 559 | { type: 'button', innerText: 'filter >100', callback: async () => filterVidsCount(100) }, 560 | ], 561 | }); 562 | 563 | const containerParent = document.querySelector('.main > .container > .content'); 564 | containerParent.innerHTML = ''; 565 | containerParent.nextElementSibling.remove(); 566 | containerParent.append(container); 567 | container.before(ignored); 568 | GM_addStyle(`.content { width: auto; } 569 | .member-videos, .ignored { background: #b3b3b324; min-height: 3rem; margin: 1rem 0px; color: #fff; font-size: 1.24rem; display: flex; flex-wrap: wrap; justify-content: center; 570 | padding: 10px; width: 100%; } 571 | .member-videos * { padding: 5px; margin: 4px; } 572 | .member-videos h2 a { font-size: 1.24rem; margin: 0; padding: 0; display: inline; } 573 | .ignored * { padding: 4px; margin: 5px; } 574 | .thumbs-items { display: flex; flex-wrap: wrap; }`); 575 | 576 | RULES.intersectionObservable = document.querySelector('.footer'); 577 | RULES.CONTAINER = container; 578 | 579 | const { friendsCount } = await getMemberData(RULES.MY_ID); 580 | 581 | RULES.paginationLast = friendsCount; 582 | 583 | const { pageGenerator, skipCurrentMember, filterVideosCount } = await getMembersVideos( 584 | RULES.MY_ID, 585 | friendsCount, 586 | (name, videosCount, id) => { 587 | container.append( 588 | parseDom(` 589 |
    590 |

    ${name} ${videosCount} videos

    591 | 592 | 593 |
    `), 594 | ); 595 | }, 596 | isPubKey ? 'public' : 'private', 597 | ); 598 | 599 | RULES.alternativeGenerator = pageGenerator; 600 | 601 | const ignoredMembers = lskdb.getAllKeys(); 602 | ignoredMembers.forEach((im) => { 603 | document 604 | .querySelector('.ignored') 605 | .append(parseDom(``)); 606 | }); 607 | 608 | const skip = (n) => { 609 | skipCurrentMember(n); 610 | document.querySelector('.thumbs-items').innerHTML = ''; 611 | }; 612 | 613 | unsafeWindow.hideMemberVideos = (e, ignore = true) => { 614 | let id = e.target.parentElement.id; 615 | if (!document.querySelector(`#${id} ~ div`)) { 616 | skipCurrentMember(); 617 | } 618 | const box = document.getElementById(id); 619 | const toDelete = [box]; 620 | let curr = box.nextElementSibling; 621 | while (curr?.classList.contains('tumbpu')) { 622 | toDelete.push(curr); 623 | curr = curr.nextElementSibling; 624 | } 625 | toDelete.forEach((e) => e.remove()); 626 | id = id.slice(4); 627 | if (ignore) { 628 | document 629 | .querySelector('.ignored') 630 | .append(parseDom(``)); 631 | lskdb.setKey(id); 632 | } 633 | }; 634 | 635 | unsafeWindow.unignore = (e) => { 636 | const id = e.target.id.slice(4); 637 | lskdb.removeKey(id); 638 | e.target.remove(); 639 | }; 640 | 641 | const filterVidsCount = (count) => filterVideosCount(count); 642 | 643 | createInfiniteScroller(store, handleLoadedHTML, RULES); 644 | } 645 | 646 | //==================================================================================================== 647 | 648 | async function clearMessages() { 649 | const sortMsgs = (doc) => { 650 | doc.querySelectorAll('.entry').forEach((e) => { 651 | const id = e.querySelector('input[name="delete[]"]').value; 652 | const msg = e.querySelector('.user-comment').innerText; 653 | if (/has confirmed|declined your|has removed/g.test(msg)) deleteMsg(id); 654 | }); 655 | }; 656 | 657 | const deleteMsg = (id) => { 658 | const url = `https://thisvid.com/my_messages/inbox/?mode=async&format=json&action=delete&function=get_block&block_id=list_messages_my_conversation_messages&delete[]=${id}`; 659 | fetch(url).then((res) => console.log(url, res?.status)); 660 | }; 661 | 662 | await Promise.all( 663 | Array.from({ length: RULES.paginationLast }, (_, i) => 664 | fetchHtml(`https://thisvid.com/my_messages/inbox/${i + 1}/`).then((html) => sortMsgs(html)), 665 | ), 666 | ); 667 | } 668 | 669 | function clearMessagesButton() { 670 | const btn = parseDom(''); 671 | btn.addEventListener('click', clearMessages); 672 | document.querySelector('.headline').append(btn); 673 | } 674 | 675 | //==================================================================================================== 676 | 677 | function route() { 678 | console.log(SponsaaLogo); 679 | 680 | if (RULES.LOGGED_IN) { 681 | defaultSchemeWithPrivateFilter.privateFilter.push({ 682 | type: 'button', 683 | innerText: 'request access 🔓', 684 | callback: requestAccess, 685 | }); 686 | } 687 | 688 | if (RULES.IS_MY_MEMBER_PAGE) { 689 | createPrivateFeed(); 690 | RULES.PAGE_HAS_VIDEO = true; 691 | } 692 | 693 | if (RULES.IS_MESSAGES_PAGE) { 694 | clearMessagesButton(); 695 | highlightMessages(); 696 | } 697 | 698 | if (RULES.IS_VIDEO_PAGE) { 699 | const holder = document.querySelector('.video-holder > p'); 700 | if (holder) { 701 | const uploader = document.querySelector('a.author').href.match(/\d+/).at(-1); 702 | holder.parentElement.append( 703 | parseDom( 704 | ``, 705 | ), 706 | ); 707 | } 708 | createDownloadButton(); 709 | } 710 | 711 | if (!RULES.PAGE_HAS_VIDEO) return; 712 | 713 | const containers = Array.from( 714 | RULES.IS_WATCHLATER_KIND 715 | ? [RULES.CONTAINER] 716 | : document.querySelectorAll('.thumbs-items:not(.thumbs-members)'), 717 | ); 718 | 719 | if (containers.length > 1 && !RULES.IS_MEMBER_PAGE) RULES.CONTAINER = containers[0]; 720 | containers.forEach((c) => { 721 | handleLoadedHTML(c, RULES.IS_MEMBER_PAGE ? c : RULES.CONTAINER, true); 722 | }); 723 | 724 | new PreviewAnimation(document.body); 725 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 726 | 727 | if (RULES.IS_OTHER_MEMBER_PAGE) { 728 | initFriendship(); 729 | } 730 | 731 | if (RULES._PAGINATION_ALLOWED) { 732 | if (!RULES.paginationElement) return; 733 | createInfiniteScroller(store, handleLoadedHTML, RULES); 734 | } 735 | } 736 | 737 | //==================================================================================================== 738 | 739 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 740 | const { state, stateLocale } = store; 741 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state); 742 | store.subscribe(applyFilters); 743 | 744 | route(); 745 | -------------------------------------------------------------------------------- /xhamster-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name XHamster Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 3.0.1 5 | // @license MIT 6 | // @description Infinite scroll. Filter by duration, include/exclude phrases. Automatically expand more videos on video page. 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.xhamster.*/* 10 | // @exclude https://*.xhamster.com/embed* 11 | // @icon https://www.google.com/s2/favicons?sz=64&domain=xhamster.com 12 | // @grant unsafeWindow 13 | // @grant GM_addStyle 14 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 15 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 16 | // @run-at document-idle 17 | // @downloadURL https://update.sleazyfork.org/scripts/493935/XHamster%20Improved.user.js 18 | // @updateURL https://update.sleazyfork.org/scripts/493935/XHamster%20Improved.meta.js 19 | // ==/UserScript== 20 | 21 | const { getAllUniqueParents, watchElementChildrenCount, waitForElementExists, timeToSeconds, exterminateVideo, Observer, sanitizeStr, DataManager, createInfiniteScroller } = window.bhutils; 22 | const { JabroniOutfitStore, defaultStateWithDurationAndPrivacy, JabroniOutfitUI, defaultSchemeWithPrivateFilter } = window.jabronioutfit; 23 | 24 | if (window.top != window.self) return; 25 | 26 | if (!/^(\w{2}.)?xhamster.(com|desi)/.test(window.location.host)) throw new Error('whatever'); 27 | 28 | const LOGO = ` 29 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡘⢲⣃⢖⡚⡴⢣⡞⠰⠁⡀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 30 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⢮⡵⣫⣝⡳⠏⢠⢃⡐⠁⡘⢀⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 31 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⡀⠀⡀⠀⠀⠀⠀⢀⠐⡀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠔⡉⢃⡉⠓⡈⢃⠊⡐⠡⡐⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 32 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⠐⠀⠄⠐⠀⡐⠀⠠⠁⡀⢁⠀⠂⠄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠂⡔⢢⡐⢣⡜⢤⠣⡜⡰⢀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 33 | ⠀⠀⠀⠀⠀⠀⠀⠠⠐⠀⠠⠐⡈⠄⡈⠐⡀⠌⢀⠐⠀⠄⡈⢂⠔⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠦⡙⣌⠣⡜⢣⡚⡥⣛⠴⣡⠃⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 34 | ⠀⠀⠀⠀⠀⠐⠈⢀⠐⠈⡐⠠⠐⠠⢀⠡⠐⡀⠂⠌⡐⠠⢐⠡⢈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠈⡐⠠⣉⠖⡱⢌⠳⣌⢣⡑⠦⠡⢏⡴⣉⠂⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 35 | ⠀⠀⠀⠀⠈⠀⠐⠀⠠⢁⠀⢂⠁⠂⠄⠂⡁⠄⢁⠂⠄⡁⢢⠘⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠈⠆⢀⠰⣀⠣⢔⡩⢜⢪⡱⢂⠇⡌⢣⠁⡞⣰⠡⢂⠀⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 36 | ⠀⠀⠀⠀⠀⠁⠂⠈⠀⠀⠈⡀⠌⠐⠈⠐⠀⠈⠄⢀⠂⠐⡀⢊⠡⠀⠀⠀⠀⠀⠀⠀⠀⠠⡈⢆⡹⠀⢂⢅⠢⣍⠒⡜⣬⠣⣕⠫⡜⢠⠃⢰⠩⡖⡑⢂⠀⡀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 37 | ⠀⠀⠀⠀⠀⠀⠀⢀⠀⠈⠀⠄⠀⠄⠂⢀⠈⠀⠀⠀⠀⠁⡀⠣⢘⠀⠀⠀⠀⠀⠀⠀⠄⡑⠌⢢⠐⡡⢌⡊⠵⡨⠝⢲⡤⡛⣌⠳⣈⠆⠡⢈⠳⣌⡑⢂⠠⠀⠂⠀⢀⠀⠀⢄⡀⡠⠀⠄⠀⠀ 38 | ⠀⠀⠀⠀⠀⠀⠀⡀⠀⢈⠀⠂⠁⡀⠠⠀⡀⢈⢄⡒⡔⣢⢔⢣⢆⢆⠤⣠⢀⡀⣀⠬⣰⠜⣎⢧⢯⡵⣫⣜⣳⣙⣎⢧⡒⡥⢌⡱⢀⠂⠁⢈⠱⡰⢌⠂⠤⢑⡈⢅⠢⢌⡑⢆⠱⣡⠋⡔⠀⠀ 39 | ⠀⠀⠀⠀⠀⠀⠀⢀⠐⠀⢀⠂⢀⠀⡄⢒⡌⠶⣘⠶⣙⢦⣛⢮⡞⣎⡗⣦⠓⡴⣡⢟⣵⡻⣽⡞⣷⢯⣗⣻⢶⣫⡞⣶⡹⣜⣣⢖⡡⢆⡀⢀⠃⠖⡨⠐⣈⠦⡘⢤⠣⠜⡌⣌⠢⢱⡘⠄⠐⠈ 40 | ⠀⠀⠀⠀⠀⠀⠈⠀⠀⠐⠀⢀⠢⣘⠰⣃⠞⣵⢫⡟⣽⣺⢽⣮⡝⡾⡼⣉⢾⣱⢯⣟⡾⣽⣳⢿⣽⣻⢾⣽⣳⢯⣽⣳⢯⣳⢳⣎⡗⢮⡐⢆⡉⢒⠡⠀⢖⡰⢉⢆⠣⣍⠲⣀⠣⣁⠎⠄⠀⢀ 41 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢆⡱⢌⡳⢭⡞⣭⢷⣻⢷⣯⣟⡾⣽⣳⡱⣝⡮⣟⣽⢾⣻⣽⣟⣯⣷⣿⣻⣾⣽⣻⡾⣽⢯⣟⡷⣞⡽⢮⣝⠲⣌⠢⢁⠂⠥⢊⡕⣊⠱⣂⠓⡄⠣⡔⡈⠀⠀⢀ 42 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣍⠲⣘⢮⡱⣏⡾⣽⢯⣟⣿⣾⡽⣿⡳⢧⡳⣝⡾⣽⢯⣿⣟⣯⣿⣿⣻⣾⣟⣷⣿⢯⣿⢯⣿⢾⣽⣻⣞⡷⣎⢿⡰⣃⠆⡈⢰⢣⡘⢤⠓⣌⢣⠐⡡⢒⠀⠁⠀⢈ 43 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⠤⣃⠳⣎⣳⣭⢿⣽⣻⡾⣿⣟⣿⢻⣝⡣⡝⣮⢿⣽⣻⣯⣿⣿⣿⣿⣿⣿⣿⣿⣻⣿⢿⣻⣯⣿⣞⣷⣻⣼⣛⡮⣵⢣⡚⢄⠘⢆⡙⢦⡙⢤⠃⢎⠡⠆⡌⠀⠀⠠ 44 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡴⣉⠗⣎⢷⣚⡿⣞⣷⣻⢷⡯⣝⠳⣪⠵⡹⣜⣻⢾⣽⣻⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣟⣷⡿⣾⣳⣟⣞⣳⡽⣲⢣⡝⢢⠌⡠⢈⠂⠝⣢⠹⣌⠱⢌⠄⠀⠁⠀ 45 | ⢣⠜⡄⢦⢀⠤⢂⠔⠀⠑⡰⢡⡛⣬⢳⢯⡽⣻⡼⢯⣳⠽⡌⢯⡔⣫⢷⡸⣝⠿⣞⡿⣯⣿⣿⡿⣿⣿⢿⣿⣽⣷⣿⣿⣻⣽⡷⣟⡾⣝⣧⢻⡱⢇⡞⡡⢎⠔⡡⢌⡐⠠⠑⠌⡱⢈⠤⠈⠄⣀ 46 | ⣂⠎⡰⢢⠍⢆⠃⢠⢩⡔⣮⠱⡸⣌⢗⣫⢞⡵⣻⢏⡧⣏⡽⣌⠲⣭⢖⡧⣫⢟⡽⣻⡽⣷⣻⢿⣻⣽⡿⣯⣿⢯⣷⣿⣻⣽⣻⣽⣛⠷⣎⠷⣙⠮⣜⡱⣊⡜⣡⠒⣌⠱⠈⠄⠡⢂⠒⡌⢰⠀ 47 | ⢍⡶⣙⢦⡙⢆⠈⣴⢣⡿⣜⡷⣥⠊⢞⡰⢫⡜⣣⠏⠶⣡⢛⡼⣛⠶⡹⢶⡙⣎⠷⣹⣝⣳⢯⣟⣯⢷⣻⢯⣟⡿⣽⣞⣯⢷⡻⢮⡹⡹⣌⠯⣕⡫⢖⣱⢱⡘⠤⠋⣄⢢⢡⠊⡔⣈⠱⡌⢃⠜ 48 | ⡟⡴⣋⠶⣙⡎⢲⣭⢿⣽⢻⡼⣣⠙⠢⣅⠣⣜⠰⣉⠖⡡⢉⠖⡥⢋⡓⢧⡙⢦⡙⠰⢪⠕⣏⠾⣜⠯⣝⢯⡞⣽⢣⠟⣬⣳⣟⣿⣳⢷⡩⢞⡱⣭⢋⡖⠣⡌⣥⢫⡔⣣⢎⡱⠒⡄⢆⠘⡂⠥ 49 | ⢿⣱⣏⡖⣣⠜⣱⢮⡷⣫⣟⡵⡃⢀⡁⠆⠓⡌⠳⠌⢎⠡⠃⠎⠐⢩⢌⣃⠹⣂⠡⠓⠤⢊⠜⡸⢌⡳⣍⠶⣙⢦⡛⣞⣳⣟⡾⣷⣟⣧⢻⣭⣓⡌⢧⡜⢧⡹⣆⡳⣙⢦⢋⠴⡉⠔⡈⢰⡉⢇ 50 | ⢯⠷⣞⣳⢬⠸⣭⢷⡻⣵⢺⡝⣁⢾⡸⣝⢶⡲⣕⠶⣎⢷⣫⢞⡽⡬⣤⢌⣣⣘⢤⡥⣆⡖⡤⣥⣀⡑⠨⠚⠥⢎⢵⣫⢷⣯⣿⣟⣾⡭⢷⣞⣧⡻⣥⢛⢧⣛⡴⢣⡍⢦⡉⢆⢁⣂⢠⢃⡜⢤ 51 | ⣳⢻⡜⣣⢎⡱⣏⢾⡳⣭⢷⡚⢬⡷⣻⡼⣧⣟⢮⣟⢮⣳⣝⡮⢷⡽⢮⣟⡶⣭⢷⣻⡼⣝⣳⡵⣫⢽⡻⢶⣞⣤⠸⣝⣯⡷⣿⣻⢾⣽⣻⡾⣽⣳⢎⡝⡎⠶⣩⠇⣜⡰⡜⣸⠲⡌⣆⠣⡜⢢ 52 | ⡷⣋⠾⣥⢫⡴⢫⢧⡛⣭⠾⡽⣣⠻⣵⢻⡵⣞⣟⡾⣏⡷⡾⣽⣛⣾⢻⣼⢻⣭⢷⣳⢟⣭⢷⡻⣝⣾⡹⢷⣎⢯⣽⣻⣞⣿⣳⢯⣟⡾⣣⣟⣷⡻⢎⡰⣉⠳⣤⢛⠴⡣⣕⢣⠣⡝⢤⠓⣍⠆ 53 | ⣭⠳⣙⠶⡩⢎⠅⡻⣜⢦⣋⠷⣩⠗⣎⢯⣽⢫⡾⡽⣽⣹⢟⣵⡻⢮⡟⣾⣹⢮⡟⣽⠾⣭⡟⣽⣫⢶⡛⢯⡴⣻⣞⡷⣯⢷⢫⢟⣼⣳⢓⡾⡱⡝⣢⠕⣎⡳⢬⣋⢞⡱⡜⢪⡕⡜⢢⡙⢤⡉ 54 | ⢦⡙⡜⢢⠓⣌⢚⡱⣌⠳⣎⠳⣍⡛⣜⡘⡮⢏⡷⣻⢵⣫⢟⡼⣹⠯⣝⢶⢫⡗⣯⠝⢯⠳⡝⢣⢍⠲⣙⠮⡵⣛⡼⡹⢎⢇⡫⡞⡵⣃⠷⣜⢣⡝⢦⡛⡴⡙⢦⡉⢦⠓⣌⠣⠜⡨⢅⡘⠤⣈ 55 | ⢆⠰⢈⠆⠱⣀⠣⢒⠌⡓⡌⠳⢄⠹⢤⠓⡘⢭⠲⡱⢎⠶⣩⠚⡥⢛⡌⢎⡱⠚⣄⢋⠆⡣⢉⠖⡨⠣⢍⠚⠥⢓⠬⡑⢎⠲⠱⣙⠲⣉⠞⣌⠣⢎⡱⢜⠲⣉⠦⡙⢦⡙⠤⢓⡘⣐⠢⢌⡐⢀ 56 | ⢀⠂⠡⢈⠐⠠⠑⣈⠢⠑⡈⢁⠊⡐⠂⠉⠜⡀⠣⠑⠊⠆⡅⢋⠔⠡⠘⠠⠂⠑⠀⡈⠀⠁⡀⢀⠀⢁⠀⠌⠠⢁⠂⠡⢈⠰⠁⢂⠡⠐⠈⠄⡉⢂⠱⢈⠱⢈⠒⠩⡐⠌⡑⠌⠰⢀⠃⠂⠄⢃ 57 | ⢀⠈⠄⠂⠈⠄⠡⠀⠄⠁⡀⠂⠀⠀⠀⠀⠀⠀⠁⠈⡐⠠⠀⠂⠐⠠⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠂⠐⠠⠈⠐⢀⠂⠐⠈⡐⠀⡈⠐⠈⠀`; 58 | 59 | class XHAMSTER_RULES { 60 | delay = 300; 61 | 62 | constructor() { 63 | this.IS_VIDEO_PAGE = /^\/videos|moments\//.test(window.location.pathname); 64 | this.paginationElement = document.querySelector('.prev-next-list, .test-pager'); 65 | this.paginationLast = parseInt(Array.from( 66 | (this.paginationElement || document).querySelectorAll('.page-button-link, .xh-paginator-button')).pop()?.innerText.replace(/\,/g, '')); 67 | Object.assign(this, this.URL_DATA()); 68 | this.CONTAINER = [...document.querySelectorAll('.thumb-list')].pop(); 69 | } 70 | 71 | IS_PRIVATE(thumb) { 72 | return !!thumb.querySelector('[data-role="video-watched'); 73 | } 74 | 75 | GET_THUMBS(html) { 76 | return html.querySelectorAll('.video-thumb'); 77 | } 78 | 79 | THUMB_URL(thumb) { 80 | return thumb.firstElementChild.href; 81 | } 82 | 83 | THUMB_IMG_DATA(thumb) { 84 | if (store?.stateLocale.pagIndexCur === 1) return ({}); 85 | const img = thumb.querySelector('img[loading]'); 86 | if (img) img.removeAttribute('loading'); 87 | if (!img?.complete || img.naturalWidth === 0) return ({}); 88 | return { img, imgSrc: img.src } 89 | } 90 | 91 | THUMB_DATA(thumb) { 92 | const title = sanitizeStr(thumb.querySelector('.video-thumb-info__name,.video-thumb-info>a')?.innerText); 93 | const duration = timeToSeconds(thumb.querySelector('.thumb-image-container__duration')?.innerText); 94 | return { title, duration } 95 | } 96 | 97 | URL_DATA() { 98 | const url = new URL(window.location.href); 99 | const paginationOffset = parseInt(url.searchParams.get('page') || url.pathname.match(/\/(\d+)\/?$/)?.pop()) || 1; 100 | 101 | const paginationUrlGenerator = n => { 102 | if (/^\/search\//.test(url.pathname)) { 103 | url.searchParams.set('page', n); 104 | } else { 105 | if (!/\/\d+\/?$/.test(url.pathname)) url.pathname = `${url.pathname}/${paginationOffset}/`; 106 | url.pathname = url.pathname.replace(/\/\d+\/?$/, `/${n}/`); 107 | } 108 | return url.href; 109 | } 110 | 111 | return { paginationOffset, paginationUrlGenerator } 112 | } 113 | } 114 | 115 | const RULES = new XHAMSTER_RULES(); 116 | 117 | //==================================================================================================== 118 | 119 | function expandMoreVideoPage() { 120 | waitForElementExists(document.body, 'button[data-role="show-more-next"]', (el) => { 121 | const observer = new Observer((target) => target.click()); 122 | observer.observe(el); 123 | }); 124 | } 125 | 126 | //==================================================================================================== 127 | 128 | function createPreviewVideoElement(src, mount) { 129 | const video = document.createElement('video'); 130 | video.playsinline = true; 131 | video.autoplay = true; 132 | video.loop = true; 133 | video.classList.add('thumb-image-container__video'); 134 | video.src = src; 135 | video.addEventListener('loadeddata', () => { 136 | mount.before(video); 137 | }, false); 138 | const stop = () => exterminateVideo(video); 139 | return { stop }; 140 | } 141 | 142 | function handleThumbHover(e) { 143 | if (!e.target.classList.contains('thumb-image-container__image')) return; 144 | const videoSrc = e.target.parentElement.getAttribute('data-previewvideo'); 145 | const { stop } = createPreviewVideoElement(videoSrc, e.target); 146 | e.target.parentElement.parentElement.addEventListener('mouseleave', stop, { once: true }); 147 | } 148 | 149 | function animate() { 150 | document.body.addEventListener('mouseover', handleThumbHover); 151 | } 152 | 153 | //==================================================================================================== 154 | 155 | function parseInPLace() { 156 | const containers = getAllUniqueParents(RULES.GET_THUMBS(document.body)); 157 | containers.forEach(c => handleLoadedHTML(c, c, false, false)); 158 | } 159 | 160 | function route() { 161 | animate(); 162 | 163 | if (RULES.IS_VIDEO_PAGE) { 164 | expandMoreVideoPage(); 165 | watchElementChildrenCount(RULES.CONTAINER, () => setTimeout(parseInPLace, 1800)); 166 | } 167 | 168 | if (RULES.paginationElement) { 169 | createInfiniteScroller(store, handleLoadedHTML, RULES); 170 | } 171 | 172 | parseInPLace(); 173 | setTimeout(parseInPLace, 500); 174 | 175 | new JabroniOutfitUI(store, defaultSchemeWithPrivateFilter); 176 | } 177 | 178 | 179 | defaultSchemeWithPrivateFilter.privateFilter = [ 180 | { type: "checkbox", model: "state.filterPrivate", label: "unwatched" }, 181 | { type: "checkbox", model: "state.filterPublic", label: "watched" }]; 182 | 183 | //==================================================================================================== 184 | 185 | console.log(LOGO); 186 | 187 | const store = new JabroniOutfitStore(defaultStateWithDurationAndPrivacy); 188 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 189 | store.subscribe(applyFilters); 190 | route(); 191 | -------------------------------------------------------------------------------- /xvideos-improved.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name XVideos Improved 3 | // @namespace http://tampermonkey.net/ 4 | // @version 2.1.1 5 | // @license MIT 6 | // @description Infinite scroll. Filter by duration, include/exclude phrases 7 | // @author smartacephale 8 | // @supportURL https://github.com/smartacephale/sleazy-fork 9 | // @match https://*.xvideos.com/* 10 | // @exclude https://*.xvideos.com/embedframe/* 11 | // @grant GM_addStyle 12 | // @require https://cdn.jsdelivr.net/npm/billy-herrington-utils@1.3.6/dist/billy-herrington-utils.umd.js 13 | // @require https://cdn.jsdelivr.net/npm/jabroni-outfit@1.4.9/dist/jabroni-outfit.umd.js 14 | // @run-at document-idle 15 | // @icon https://www.google.com/s2/favicons?sz=64&domain=xvideos.com 16 | // @downloadURL https://update.sleazyfork.org/scripts/494005/XVideos%20Improved.user.js 17 | // @updateURL https://update.sleazyfork.org/scripts/494005/XVideos%20Improved.meta.js 18 | // ==/UserScript== 19 | 20 | const { DataManager, createInfiniteScroller, sanitizeStr, timeToSeconds, parseDom } = window.bhutils; 21 | const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit; 22 | 23 | const LOGO = ` 24 | ⡐⠠⠀⠠⠐⡀⠆⡐⠢⡐⠢⡁⢆⠡⢂⠱⡈⠔⣈⠒⡌⠰⡈⠔⢢⢁⠒⡰⠐⢢⠐⠄⣂⠐⡀⠄⢂⠰⠀⢆⡐⠢⢐⠰⡀⠒⢄⠢⠐⣀⠂⠄⢀⢂⠒⡰⠐⡂⠔⠂⡔⠂⡔⠂⡔⠂⡔⠂⢆⠡ 25 | ⠠⡁⠂⠄⠐⡀⠆⡁⠆⡑⠤⢑⡈⢆⠡⢂⠱⡈⡄⠣⢌⠱⡈⠜⡠⢊⠔⡁⢎⡐⠌⡂⢄⠂⠄⠈⠄⢂⡉⠤⢀⠃⡌⡐⠤⠉⡄⠂⠥⠀⠌⡀⠰⢈⠒⢠⠑⡈⠔⡡⠂⢅⢂⠱⢀⠣⡐⢩⢀⠣ 26 | ⢁⢂⡉⠐⡀⠀⠂⠔⠂⡔⢈⠆⠰⡈⢆⠡⢂⠱⣈⠱⡈⢆⠱⡈⠔⡡⢊⠔⡂⠔⡨⠐⠄⢊⠐⠈⡐⠠⠐⢂⠡⠌⡐⠄⠢⠑⡠⠉⢄⠁⠂⠄⢂⠡⢊⡐⢂⠡⠊⢄⠱⠈⡄⢊⠄⡃⠔⡡⠌⢒ 27 | ⡂⠆⢨⠐⠠⢀⠁⠌⡐⠄⢊⠄⡃⠜⣀⠣⠌⡒⢄⠣⡘⢄⠣⡘⠤⡑⢌⠰⡈⢆⠁⢎⠈⠤⢈⠀⠄⠡⠘⡀⠆⠒⠠⢈⠁⠆⠡⠌⠠⢈⠐⠀⠌⡐⠂⡔⠨⡐⢉⠄⣊⠡⡐⢡⠊⠔⡡⢂⠍⡂ 28 | ⠐⡉⢄⢊⢁⠂⠄⠐⠠⠘⢠⠘⡀⢎⠠⢂⠱⡈⠆⡑⢌⠢⡑⢄⠣⡘⢄⠣⡐⢌⡘⠠⡉⠐⡀⠂⢀⠁⠂⠔⡈⠠⠁⠂⢈⠠⠁⠌⡐⠀⠂⠀⠌⡐⠡⠄⢃⠌⡂⠜⡀⢆⠑⣂⠱⡈⠔⡡⢂⠱ 29 | ⢃⠜⢠⠊⢄⢊⠐⠠⠀⡁⠆⢂⠡⠂⡅⢊⠔⡁⢎⠰⡈⢆⠱⡈⢆⠱⡈⢆⠡⢂⠔⠡⡐⠡⠐⠀⠀⠌⠐⠠⠀⠐⠀⠂⠀⡀⠈⠀⠀⢁⠀⠀⠂⠌⡐⡉⢄⠢⢑⠨⡐⠌⡒⢄⠢⡑⢌⠰⡁⢆ 30 | ⠆⡘⢄⠊⡔⡈⠌⡄⢁⠀⠌⠠⠡⠑⡠⢃⠰⢈⠢⢡⠘⠄⡃⠜⡠⢃⠔⡈⢆⠡⠊⠔⡁⢂⠡⠀⠈⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⡁⢂⠡⡐⠌⡰⢈⠂⡅⢊⠔⡨⠐⡌⢢⠡⡘⢠ 31 | ⠰⢡⢈⠒⠤⢑⠨⡐⠄⢂⠀⠁⠆⡑⢠⠂⡡⠊⢄⠃⡜⢠⠑⡌⠰⣈⠢⢑⡈⠆⢩⠐⡐⡀⢂⠀⠀⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⢀⠂⢡⠐⢡⢂⠡⢊⠰⣁⠊⡔⢡⠘⠤⢑⡈⢆ 32 | ⠁⠆⡌⡘⠰⡈⠆⢡⠘⡠⠌⢀⠂⠰⢀⠂⢅⡘⠠⢊⠐⡂⠥⡈⠥⠐⠌⡂⠔⡉⢄⠂⢡⠐⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠁⢀⠂⠌⠄⢊⠄⠢⡑⡈⠆⡄⢣⠈⢆⠩⡐⠢⢌⠰ 33 | ⠩⡐⠤⢁⠣⡐⡉⢄⢊⡐⠌⠠⢀⠁⠂⢌⠠⠐⡡⢂⠡⢘⡀⠆⡡⢉⠂⢅⠊⡐⠄⡉⠄⢂⠁⠄⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠐⠀⠄⠠⢈⠐⣈⠢⠌⢡⠐⡡⢊⡐⢂⠍⡄⢃⠌⡡⠂⠥ 34 | ⡑⠄⠣⠌⡂⢅⡘⢠⠂⠔⣈⠡⠂⠄⠁⠂⠄⢃⠐⠤⠑⢂⠰⠈⠤⢁⠊⢄⠊⡐⠌⡐⠈⠄⠂⢀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⡈⠄⠐⠀⢂⠐⡀⠢⠘⡠⠑⡠⢁⠆⡡⢊⠰⢈⠰⢁⠩⡐ 35 | ⢆⡉⠆⡑⠌⡄⢢⠁⡜⠐⡠⠂⠥⠈⠄⠈⡐⢀⠊⠄⠡⠊⠄⡉⡐⡈⠔⠂⠌⡐⠠⢀⠁⠂⠌⠀⢀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠠⠐⠀⡀⠂⠈⢀⠂⠄⠡⢁⠄⢃⠰⢁⠢⢁⠆⢡⠊⠰⡈⠔⣀ 36 | ⢄⠢⡑⠌⡒⢨⠐⡌⢠⠃⠤⠑⢂⠉⠄⠡⢀⠂⠌⡀⠃⠌⡐⠠⢁⡐⠈⠤⠁⠄⡁⠂⠌⠐⠠⠈⠀⠀⠐⠀⠀⡀⠀⠀⠀⠀⠀⠄⠂⠀⡀⠁⡀⠂⠌⡐⠠⠈⠄⠒⡈⠄⠃⡌⠄⢊⠡⠐⢂⠄ 37 | ⣈⠒⢌⠰⡁⢆⠱⢈⠔⣈⠢⠑⣈⠰⠈⠄⡉⢿⣶⣤⣁⠒⠠⢁⠂⠄⡁⢂⠁⠂⠄⠡⠈⡐⠀⠂⢁⠈⠀⠠⠀⠀⠀⢀⠁⠀⠂⠀⠠⠀⠀⠐⠀⠌⠀⠄⠡⠈⠄⠃⠄⣉⠰⠀⠎⡐⢈⠡⢂⠘ 38 | ⢆⡘⠄⡃⢌⠢⡘⢄⠊⡄⠢⠑⣀⠢⠁⠌⡐⠀⠻⣿⣿⣿⣷⣦⣌⡐⢀⠂⠌⡐⠈⠄⡁⠠⢈⠐⠀⡀⠂⠀⢀⠀⠀⠠⠈⢀⡀⠈⠀⠀⠁⠠⠈⡀⠡⠈⠄⠡⢈⠐⡈⠄⢂⠁⠒⡈⠄⠒⡈⣄ 39 | ⢀⠢⠑⡌⢂⠱⡈⢄⠣⡐⡁⢣⠀⠆⣉⠐⡠⢁⠂⠹⣟⣾⢳⣯⡟⣿⢯⡶⣦⣤⣁⠂⠄⡁⢀⠂⡐⣀⣄⡬⡴⢺⣀⡁⠄⣾⡹⢶⡖⢧⣀⡁⠠⢀⠁⠂⠌⡐⢀⠂⡐⠈⠄⣈⢡⣰⣬⢷⣟⣿ 40 | ⠠⠌⡑⢠⠃⢆⡑⠌⡰⠠⢑⠠⡑⡈⢄⢂⡁⢂⠌⠠⠹⣞⡽⡲⢏⡷⣋⠷⡳⡼⣩⢟⡹⡍⣏⠽⣡⠳⣜⢲⡙⢧⠣⣜⢣⠧⣙⢦⡙⢮⠱⣙⠒⢦⠨⡔⣠⠐⣄⢢⢤⡙⢶⡩⢟⠶⣹⢞⡼⣺ 41 | ⢄⠊⡔⢡⠘⡄⢢⠑⡄⠣⠌⠒⡠⢁⢂⠂⡌⡐⡈⠆⣁⠺⡴⢫⡝⡲⢍⡳⣙⠲⣅⠺⡰⠱⡈⠆⡅⠣⢌⠢⣉⠆⠳⣌⠣⢇⠣⢎⡘⢆⠓⠌⡡⢂⠡⡐⢠⠁⠆⡌⢢⠙⢢⡙⢮⡹⡱⢎⡳⣍ 42 | ⢀⠃⡌⢂⠥⠘⡄⢊⠤⠑⡨⢁⠔⡁⢢⠡⡐⠤⡑⠌⡄⢣⢍⡣⡜⡱⣍⠲⡡⠓⡌⠱⡐⡡⢘⠰⣈⠱⣈⠒⠤⣉⠓⠤⢋⡜⢄⠣⡘⢌⠪⠔⡡⢂⠡⢂⠅⡊⠔⡈⢆⣉⠒⡌⠦⢱⡉⢮⡱⣹ 43 | ⠢⡑⡈⠆⡌⠱⢈⠆⢢⠑⠄⣃⠰⢈⠔⣂⠑⢢⠑⡌⠰⣡⠚⡴⢡⠓⡌⡱⠐⡍⡐⢣⡐⡡⢎⡐⢂⢃⠒⡌⠒⡤⢉⢎⡡⠜⢢⠑⡌⢢⠑⠬⡐⢥⢊⡔⢨⡐⡡⡑⠢⡄⢋⡔⡉⢆⡩⢆⡱⢢ 44 | ⡐⢡⠘⡰⢈⡑⠢⡘⠄⡊⠔⡠⢃⠌⡒⠤⢉⠂⠥⢐⡁⠦⡙⢤⢃⠣⢜⡠⢋⠔⡡⢃⠴⡁⠦⡘⣌⢊⠵⣈⠓⡌⢎⡰⢢⡙⢆⡣⠜⡤⢋⢖⡩⢆⢣⡜⣡⠖⣡⠜⣡⠘⢢⠰⡉⢆⠲⠌⡔⠣ 45 | ⠨⠄⡃⠔⠡⣀⠃⡄⢃⠌⢢⠑⠌⢢⠑⡌⢂⠍⢢⢡⠘⡰⢉⠖⣈⠣⢆⠱⢌⢊⡱⢌⢢⡙⠴⡱⢌⢎⢲⢡⢫⠜⣆⠳⣥⢋⢮⡱⢫⡜⣭⢲⡱⣋⠶⣩⢖⡹⢢⡝⣢⠝⣢⢃⡕⢊⡜⣘⠰⢩ 46 | ⠐⠌⡐⢈⠡⢀⠒⡈⠤⠘⡄⢊⡜⢠⢃⠜⡨⠌⣅⠢⢍⢒⡉⠦⢡⢃⠎⡜⣂⢣⠒⡌⢦⡑⣣⠱⢎⡎⣎⢧⢣⡛⣬⣓⢮⣙⢦⡝⣧⣛⢶⣣⠷⣭⢳⢧⡺⡱⣇⠞⣥⡚⡵⢪⡜⣥⠒⡬⢡⢣ 47 | ⠀⢄⠐⡠⢂⠆⢢⠑⡌⡱⢈⠥⣘⢂⠎⢢⡑⡩⢄⡓⡌⠦⡑⡍⢦⡉⢞⡰⢡⢎⡱⣍⠶⣉⠶⣙⠮⡼⡜⣎⠧⣝⠶⣭⠞⣭⢞⡽⣲⡝⣮⢳⡻⣜⢧⡳⣝⡳⣎⢟⢦⡽⣘⢧⡚⡴⣋⢖⡣⢎ 48 | ⠨⢄⠣⣐⠡⢊⡔⠡⢎⠰⣉⠲⢄⡋⢬⡑⠴⣑⠪⡔⣌⢣⠱⡘⢦⡙⢦⢱⢋⢦⠳⡜⢮⡱⣋⣎⠷⣱⢏⡾⣹⢎⡿⣜⡻⣜⢯⡞⣵⢻⡜⣧⢻⡜⣧⢻⡜⣧⡝⣮⠳⣜⡱⢎⡵⢣⡙⢮⡱⢎ 49 | ⢁⠎⡐⠆⡥⢃⡌⠓⡬⢑⢢⢃⠎⡜⢢⠜⡱⢌⡱⡘⡤⢣⡙⡜⢦⡹⢜⡪⣝⢪⢳⣙⠶⣳⡹⣬⢻⡱⣏⢾⡱⢯⡞⣵⡻⣝⡮⡽⣎⢷⡹⣎⢷⡹⣎⢷⡹⡖⡽⣒⡻⣌⡳⣍⢖⡣⡝⢦⠹⡜ 50 | ⢊⠬⡑⢎⡰⢡⢊⠵⣈⠇⡎⡜⡸⢌⠣⢎⡱⢊⡴⢣⢱⢣⡹⠜⣦⡙⢮⡱⢎⢧⡳⢭⡞⡵⣳⢭⡳⣝⢮⡳⣏⡷⣹⢧⣻⣜⣳⡝⣮⢳⡻⣜⢧⡻⣜⢧⣛⡽⣜⢣⡗⣥⠳⡜⢮⡑⢮⣑⢫⢜`; 51 | 52 | 53 | class XVIDEOS_RULES { 54 | delay = 300; 55 | 56 | constructor() { 57 | this.paginationElement = [...document.querySelectorAll('.pagination')].pop(); 58 | this.paginationLast = parseInt(document.querySelector('.last-page')?.innerText) || 1; 59 | Object.assign(this, this.URL_DATA()); 60 | this.CONTAINER = document.querySelector('#content')?.firstElementChild; 61 | this.HAS_VIDEOS = !!document.querySelector('div.thumb-block[id^=video_]'); 62 | } 63 | 64 | GET_THUMBS(html) { return html.querySelectorAll('div.thumb-block[id^=video_]:not(.thumb-ad)'); } 65 | 66 | THUMB_IMG_DATA() { return ({}); }; 67 | 68 | THUMB_URL(thumb) { return thumb.querySelector('.title a').innerText; } 69 | 70 | THUMB_DATA(thumb) { 71 | const uploader = sanitizeStr(thumb.querySelector('[class*=name]')?.innerText); 72 | const title = sanitizeStr(thumb.querySelector('[class*=title]')?.innerText) 73 | .concat(uploader ? ` user:${uploader}` : ""); 74 | const duration = timeToSeconds(sanitizeStr(thumb.querySelector('[class*=duration]')?.innerText)); 75 | 76 | setTimeout(() => { 77 | const id = parseInt(thumb.getAttribute('data-id')); 78 | unsafeWindow.xv.thumbs.prepareVideo(id); 79 | }, 200); 80 | 81 | return { title, duration } 82 | } 83 | 84 | URL_DATA() { 85 | const url = new URL(window.location.href); 86 | const paginationOffset = parseInt(url.searchParams.get('p') || url.pathname.match(/\/(\d+)\/?$/)?.pop()) || 0; 87 | if (!url.searchParams.get('k')) { 88 | if (url.pathname === '/') url.pathname = '/new'; 89 | if (!/\/(\d+)\/?$/.test(url.pathname)) url.pathname = `${url.pathname}/${paginationOffset}/`; 90 | } 91 | 92 | const paginationUrlGenerator = (n) => { 93 | if (url.searchParams.get('k')) { 94 | url.searchParams.set('p', n); 95 | } else { 96 | url.pathname = url.pathname.replace(/\/(\d+)\/?$/, `/${n}/`); 97 | } 98 | return url.href; 99 | } 100 | 101 | return { paginationOffset, paginationUrlGenerator } 102 | } 103 | } 104 | 105 | const RULES = new XVIDEOS_RULES(); 106 | 107 | //==================================================================================================== 108 | 109 | function createPreviewElement(src, mount) { 110 | const elem = parseDom(` 111 | `); 114 | 115 | mount.after(elem); 116 | const video = elem.querySelector('video'); 117 | video.src = src; 118 | video.addEventListener('loadeddata', () => { 119 | mount.style.opacity = '0'; 120 | elem.style.display = 'block'; 121 | elem.style.background = '#000'; 122 | }, false); 123 | 124 | return { 125 | removeElem: () => { 126 | video.removeAttribute('src'); 127 | video.load(); 128 | elem.remove(); 129 | mount.style.opacity = '1'; 130 | } 131 | }; 132 | } 133 | 134 | function getVideoURL(src) { 135 | return src 136 | .replace(/thumbs169l{1,}/, 'videopreview') 137 | .replace(/\/\w+\.\d+\.\w+/, '_169.mp4') 138 | .replace(/(-\d+)_169\.mp4/, (_, b) => `_169${b}.mp4`) 139 | } 140 | 141 | function animate() { 142 | function handleThumbHover(e) { 143 | if (!(e.target.tagName === 'IMG' && e.target.id.includes('pic_'))) return; 144 | const videoSrc = getVideoURL(e.target.src); 145 | const { removeElem } = createPreviewElement(videoSrc, e.target); 146 | e.target.parentElement.parentElement.parentElement.addEventListener('mouseleave', removeElem, { once: true }); 147 | } 148 | 149 | RULES.CONTAINER.addEventListener('mouseover', handleThumbHover); 150 | } 151 | 152 | //==================================================================================================== 153 | 154 | function route() { 155 | if (RULES.paginationElement) { 156 | createInfiniteScroller(store, handleLoadedHTML, RULES); 157 | } 158 | 159 | if (RULES.HAS_VIDEOS) { 160 | animate(); 161 | handleLoadedHTML(RULES.CONTAINER) 162 | new JabroniOutfitUI(store); 163 | } 164 | } 165 | 166 | //==================================================================================================== 167 | 168 | console.log(LOGO); 169 | 170 | const store = new JabroniOutfitStore(defaultStateWithDuration); 171 | const { applyFilters, handleLoadedHTML } = new DataManager(RULES, store.state); 172 | store.subscribe(applyFilters); 173 | 174 | route(); 175 | --------------------------------------------------------------------------------