PornPics Enhancer

A framework to integrate various porn gallery sites - providing the ultimate porn gallery browsing experience.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         PornPics Enhancer
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      2.0.1
// @description  A framework to integrate various porn gallery sites - providing the ultimate porn gallery browsing experience.
// @match        https://www.pornpics.com/*
// @match        https://www.pichunter.com/*
// @match        https://yespornpics.com/*
// @match        https://babesource.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ncga.com
// @grant        GM_xmlhttpRequest
// @grant        GM.addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/umd/photoswipe.umd.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/masonry/4.2.2/masonry.pkgd.min.js
// @connect      pornpics.com
// @connect      pichunter.com
// @connect      yespornpics.com
// @connect      babesource.com
// ==/UserScript==

(function() {
    'use strict';

    const cornpics_url = 'https://www.pornpics.com/cornpics/';
    if (location != cornpics_url) {
        location = cornpics_url;
    }

    GM.addStyle(`
@import url('https://cdnjs.cloudflare.com/ajax/libs/photoswipe/5.3.7/photoswipe.min.css');

html {
  scroll-behavior: smooth;
}

.next-button#fb-button {
  display: block;
}

.pswp img {
  object-fit: contain;
}

.site-select {
  padding: 0 3px;
  text-align: center;
}

.grid {
  margin: 0 auto;
}

.grid-item {
  width: 300px;
  border: 1px solid #ccc;
  border-radius: 5px;
  margin: 3px;
}

.gallery-title {
  color: #5a5a5a;
  flex-grow: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 28px;
  padding: 0 10px;
}

.dark .gallery-title {
  color: #cfcfcf;
}

.grid-item img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 5px;
  cursor: pointer;
}

.gallery-title-container {
  display: flex;
  justify-content: center;
}

.gallery-title {
  white-space: nowrap;
  overflow-x: auto;
}

.gallery-title::-webkit-scrollbar {
  display: none;
}

.info-row {
  display: flex;
  align-items: center;
  margin: 2px 0;
}

.info-title {
  white-space: nowrap;
  color: #5a5a5a;
  font-size: 20px;
  margin: 0 3px 0 5px;
}

.dark .info-title {
  color: #cfcfcf;
}

.info-button-container {
  display: flex;
  overflow-x: auto;
}

.info-button-container::-webkit-scrollbar {
  display: none;
}

.info-button {
  white-space: nowrap;
  background-color: #676767;
  border-radius: 3px;
  color: #fff;
  margin: 0 1px;
  padding: 3px;
  transition: background-color 0.3s ease;
}

.info-button:hover {
  background-color: #4d4d4d;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
`);

    class BaseSite {
        constructor() {
            $('base').remove();
            $('.site-var-container').remove();
        }
        get_inputs() {}
        get_url() {}
        parse_galleries(data, callback) {}
        get_thumb_info(element) {}
        get_title_str(data) {}
        get_info_dict(data) {}
        get_image_list(data) {}
        set_vars(element) {}
        set_first_page() {}
        set_next_page() {}
    };

    class PornPics extends BaseSite {
        constructor() {
            super();
            this.key = '';
            this.key_elem = $('<input>').addClass('search__text inpt-default');
            this.latest = false;
            this.limit = 20;
            this.offset = 0;
        }
        get_inputs() {
            return $('<div>').addClass('site-var-container').append(this.key_elem);
        }
        get_url() {
            this.key = this.key_elem.val();
            return `https://www.pornpics.com/search/srch.php` +
                `?q=${encodeURIComponent(this.key)}` +
                `${this.latest ? '&date=latest' : ''}` +
                `&limit=${this.limit}` +
                `&offset=${this.offset}`;
        }
        parse_galleries(data, callback) {
            const json_array = $.parseJSON(data);
            json_array.forEach(callback);
        }
        get_thumb_info(element) {
            return [element.g_url, element.t_url_460, element.desc];
        }
        get_title_str(data) {
            return $(data).find('.title-section h1').text();
        }
        get_info_dict(data) {
            return {
                '📺': $(data).find(".gallery-info__item").eq(0).find("a"),
                '👠': $(data).find(".gallery-info__item").eq(1).find("a"),
                '📚': $(data).find(".gallery-info__item.tags").eq(0).find("a"),
                '🏷️': $(data).find(".gallery-info__item.tags").eq(1).find("a")
            };
        }
        get_image_list(data) {
            const image_elements = $(data).find('#main ul li a');
            const image_list = [];
            image_elements.each(function() {
                const src = $(this).attr('href');
                const [width, height] = $(this).attr('data-size').split('x');
                image_list.push({src: src,
                                 width: width,
                                 height: height});
            });
            return image_list;
        }
        set_vars(element) {
            this.key_elem.val($(element).text());
        }
        set_first_page() {
            this.offset = 0;
        }
        set_next_page() {
            this.offset += this.limit;
        }
    };

    class PicHunter extends BaseSite {
        constructor() {
            super();
            this.base_url = 'http://pichunter.com';
            $('head').append($('<base>').attr('href', this.base_url));
            this.type = '';
            this.type_elem = $('<select>').addClass('search__submit btn-light')
                .append($('<option>').text('models'))
                .append($('<option>').text('sites'))
                .append($('<option>').text('tags'))
                .css('border-radius', '3px 0 0 3px')
                .css('text-align', 'center');
            this.key = '';
            this.key_elem = $('<input>').addClass('search__text inpt-default')
                .css('width', 'calc(100% - 140px)')
                .css('border-radius', '0')
                .css('border-left', 'none');
            this.page = 1;
        }
        get_inputs() {
            return $('<div>').addClass('site-var-container').append(this.type_elem).append(this.key_elem);
        }
        get_url() {
            // TODO: add support for 'search' type, add support for sorting
            this.type = this.type_elem.val();
            this.key = this.key_elem.val();
            return `https://www.pichunter.com/` +
                `${this.type}/` +
                `${this.type == 'models' ? '' : 'all/'}` +
                `${encodeURIComponent(this.key)}/` +
                `${this.type == 'models' ? 'photos/' : ''}` +
                `${this.page}/format/json`;
        }
        parse_galleries(data, callback) {
            const json_array = $.parseJSON(data).thumbs;
            json_array.forEach(callback);
        }
        get_thumb_info(element) {
            return [this.base_url + element.galUrl, element.src, element.title2];
        }
        get_title_str(data) {
            return $(data).find('h1').text();
        }
        get_info_dict(data) {
            const tags = $(data).find('.tagCloud');
            return {
                '📺': tags.find('li.g_site a'),
                '👠': tags.find('li.g_star a'),
                '📚': tags.find("li:not([class]) a"),
            };
        }
        get_image_elements(data) {
            return $(data).find('.flex-images figure a');
        }
        get_image_list(data) {
            const image_elements = $(data).find('.flex-images figure a');
            const image_list = [];
            image_elements.each(function() {
                image_list.push({src: $(this).attr('href'),
                                 width: $(this).find('img').attr('xow'),
                                 height: $(this).find('img').attr('xoh')});
            });
            return image_list;
        }
        set_vars(element) {
            const [type, key] = $(element).attr('href').substr(1).split('/');
            this.type_elem.val(type);
            this.key_elem.val(key.replaceAll('_', ' '));
        }
        set_first_page() {
            this.page = 1;
        }
        set_next_page() {
            ++this.page;
        }
    }

    class YesPornPics extends BaseSite {
        constructor() {
            super();
            this.base_url = 'https://yespornpics.com';
            $('head').append($('<base>').attr('href', this.base_url));
            this.key = '';
            this.key_elem = $('<input>').addClass('search__text inpt-default');
            this.page = 1;
        }
        get_inputs() {
            return $('<div>').addClass('site-var-container').append(this.key_elem);
        }
        get_url() {
            this.key = this.key_elem.val();
            return `https://yespornpics.com/sex/` +
                `${encodeURIComponent(this.key)}/` +
                `${this.page}`;
        }
        parse_galleries(data, callback) {
            const galleries = $(data).find('.gallery a');
            galleries.each(function () {
                callback(this);
            });
        }
        get_thumb_info(element) {
            return [this.base_url + $(element).attr('href'), $(element).find('img').attr('src'), $(element).find('img').attr('alt')];
        }
        get_title_str(data) {
            return $(data).find('.jpeg a img').attr('alt').slice(0, -5);
        }
        get_info_dict(data) {
            const tags = $(data).find('.gallerytags');
            return {
                '📺': tags.find('h4 a'),
                '👠': tags.find('h5 a'),
                '📚': tags.find('h6 a'),
            };
        }
        get_image_list(data) {
            const image_elements = $(data).find('.jpeg a');
            const image_list = [];
            image_elements.each(function() {
                image_list.push({src: $(this).attr('href')});
            });
            return image_list;
        }
        set_vars(element) {
            const key = $(element).attr('href').split('/')[2];
            this.key_elem.val(key);
        }
        set_first_page() {
            this.page = 1;
        }
        set_next_page() {
            ++this.page;
        }
    }

    class BabeSource extends BaseSite {
        constructor() {
            super();
            this.key = '';
            this.key_elem = $('<input>').addClass('search__text inpt-default');
            $('.input-container').append(this.key_elem);
            this.page = 1;
            this.search_mode = true;
            this.jump_url = '';
        }
        get_inputs() {
            return $('<div>').addClass('site-var-container').append(this.key_elem);
        }
        get_url() {
            // TODO: sort by views
            var res;
            if (this.search_mode) {
                this.key = this.key_elem.val();
                res = `https://babesource.com/search/photos/` +
                    `${encodeURIComponent(this.key)}/`;
            } else {
                this.search_mode = true;
                res = this.jump_url;
            }
            res += `page${this.page}.html`;
            return res;
        }
        parse_galleries(data, callback) {
            const galleries = $(data).find('.main-content__card-link');
            galleries.each(function () {
                callback(this);
            });
        }
        /*async get_thumb_info(element) {
            const thumbnail_url = $(element).find('picture img').attr('data-src');
            return [$(element).attr('href'),
                    await get_base64_image(thumbnail_url),
                    $(element).find('picture img').attr('alt')];
        }*/
        get_thumb_info(element) {
            return [$(element).attr('href'),
                    $(element).find('picture img').attr('data-src'),
                    $(element).find('picture img').attr('alt')];
        }
        get_title_str(data) {
            return '';
        }
        get_info_dict(data) {
            return {
                '📺': $(data).find('div.aside-setting__chapter:has(h4:contains("Paysite")) a'),
                '👠': $(data).find('div.aside-setting__chapter:has(h4:contains("Models")) a'),
                '📚': $(data).find('div.aside-setting__chapter:has(h4:contains("Categories")) a'),
                '🏷️': $(data).find('.aside-setting__category-link'),
            };
        }
        get_image_elements(data) {
            return $(data).find('.slideshowGalleryImage');
        }
        /*async get_image_list(data) {
            const image_elements = $(data).find('.slideshowGalleryImage');
            const image_list = [];
            const promises = [];
            image_elements.each(async function() {
                promises.push(get_base64_image($(this).attr('href')).then((encoded_src) => {
                    image_list.push({src: encoded_src});
                }));
            });
            await Promise.all(promises);
            return image_list;
        }*/
        get_image_list(data) {
            const image_elements = $(data).find('.slideshowGalleryImage');
            const image_list = [];
            image_elements.each(function() {
                image_list.push({src: $(this).attr('href')});
            });
            return image_list;
        }
        set_vars(element) {
            this.key_elem.val('');
            this.jump_url = $(element).attr('href');
            this.search_mode = false;
        }
        set_first_page() {
            this.page = 1;
        }
        set_next_page() {
            ++this.page;
        }
    }

    function get_responce(url, type) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                url: url,
                responseType: type,
                onload: function(response) {
                    resolve(response.response);
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }

    /*async function get_base64_image(url) {
        const blob = await get_responce(url, 'blob');
        return new Promise((resolve, _) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.readAsDataURL(blob);
        });
    }*/

    async function get_galleries(url) {
        const data = await get_responce(url, 'text');
        site.parse_galleries(data, async (element) => {
            const [gallery_url, thumbnail_src, thumbnail_alt] = site.get_thumb_info(element);
            const gallery_data = await get_responce(gallery_url, 'text');
            const title_str = site.get_title_str(gallery_data);
            const info_dict = site.get_info_dict(gallery_data);
            const title = $('<div>')
            .addClass('gallery-title-container')
            .append(
                $('<div>').addClass('gallery-title')
                .text(title_str)
            );
            const img = $('<img>')
            .attr('src', thumbnail_src)
            .attr('alt', thumbnail_alt)
            .on('click', function() {
                const image_list = site.get_image_list(gallery_data);
                const pswp = new PhotoSwipe({dataSource: image_list});
                pswp.init();
            })
            .on('load', () => {
                grid.masonry('layout');
            });
            const info = get_info(info_dict);
            const grid_item = $('<div>')
            .addClass('grid-item')
            .append(title)
            .append(img)
            .append(info);
            grid.append(grid_item).masonry('appended', grid_item);
        });
    }

    function get_info(info_dict) {
        const info = $('<div>').addClass('info');
        for (const [key, elements] of Object.entries(info_dict)) {
            if (elements.length === 0) {
                continue;
            }
            const info_title = $('<div>').addClass('info-title').text(key);
            const info_buttons = $('<div>').addClass('info-button-container');
            elements.each(function () {
                info_buttons.append(
                    $('<button>').addClass('info-button').text($(this).text().trim()).on('click', () => {
                        site.set_vars(this);
                        update_page();
                    })
                );
            });
            const info_row = $('<div>').addClass('info-row').append(info_title).append(info_buttons);
            info.append(info_row);
        }
        return info;
    }

    // next-button
    $('#fb-button').remove();
    const next_button = $('<span>').addClass('next-button').attr('id', 'fb-button').text('Next Page').on('click', function() {
        site.set_next_page();
        update_page();
    });
    next_button.insertBefore('footer').hide();

    // nav-section
    $('.nav-button-menu').remove();
    $('.left-side').remove();
    const url_display = $('<a>');
    $('.nav-section').prepend($('<div>').addClass('left-nav-menu').append($('<ul>').append($('<li>').append(url_display))));
    $('.right-side .item:not(.site-theme)').remove();

    // head
    $('head title').text('Corn Pics');
    $('head meta[name="referrer"]').attr('content', 'no-referrer');

    // footer
    $('footer').remove();

    function clear_galleries() {
        grid.masonry('remove', grid.find('.grid-item'));
        grid.empty();
        next_button.hide();
        url_display.text('');
    }

    function update_url() {
        const url = site.get_url();
        url_display.text(url);
        return url;
    }

    function update_page() {
        clear_galleries();
        get_galleries(update_url());
        window.scrollTo(0, 0);
        next_button.show();
    }

    $(window).on('resize', function() {
        grid.masonry('layout');
    });

    let site = new PornPics();
    const grid = $('<div>').addClass('grid').addClass('clearfix').masonry({
        itemSelector: '.grid-item',
        fitWidth: true
    });

    // header-section
    const go_button = $('<button>').addClass('search__submit btn-light').text('Go').on('click', function() {
        site.set_first_page();
        update_page();
    });
    const control_section = $('<div>').addClass('search-section').append(site.get_inputs()).append(go_button);
    const site_select = $('<select>').addClass('site-select btn-fill')
    .append($('<option>').text('PornPics'))
    .append($('<option>').text('PicHunter'))
    .append($('<option>').text('YesPornPics'))
    .append($('<option>').text('BabeSource'))
    .on('change', function() {
        switch ($(this).val()) {
            default:
            case 'PornPics':
                site = new PornPics();
                break;
            case 'PicHunter':
                site = new PicHunter();
                break;
            case 'YesPornPics':
                site = new YesPornPics();
                break;
            case 'BabeSource':
                site = new BabeSource();
                break;
        }
        clear_galleries();
        control_section.prepend(site.get_inputs());
    });
    $('.nav-button-search').remove();
    $('.search-section').remove();
    $('.user-section').remove();
    $('.login-section').remove();
    $('.header-section').append(control_section).append(
        $('<div>').addClass('login-section active').append(site_select)
    );

    $('#content').remove();
    grid.insertAfter('#wrapper nav');



})();