├── .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 |
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 |
112 |
113 |
`);
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 |
--------------------------------------------------------------------------------