Greasy Fork is available in English.

Iwara Custom Sort

Automatically sort teaser images on /videos, /images, /subscriptions, /users, /playlist, and sidebars using customizable sort function. Can load and sort multiple pages at once.

Versione datata 14/08/2019. Vedi la nuova versione l'ultima versione.

// ==UserScript==
// @name     Iwara Custom Sort
// @name:ja  Iwara Custom ソート
// @version  0.206
// @grant    GM.setValue
// @grant    GM.getValue
// @grant    GM.deleteValue
// @run-at   document-end
// @match    https://ecchi.iwara.tv/*
// @match    https://www.iwara.tv/*
// @match    http://ecchi.iwara.tv/*
// @match    http://www.iwara.tv/*
// @description  Automatically sort teaser images on /videos, /images, /subscriptions, /users, /playlist, and sidebars using customizable sort function. Can load and sort multiple pages at once.
// @description:ja /videos、/images、/subscriptions、/users、/playlistとサイドバーのサムネイルを自動的にソートします。ソート方法はカスタマイズすることができます、一度に複数のページを読み込んでソートすることができます。
// @license  AGPL-3.0-or-later
// @require https://cdn.jsdelivr.net/npm/sweetalert2@8.15.3/dist/sweetalert2.all.min.js#sha256-4/EaXPJ/6N3TkeW1FnAqmfV7JNVmnIFQ3bllkklPJ9U=
// @require https://unpkg.com/loglevel@1.6.3/dist/loglevel.min.js#sha384-Op9lLc4V1M516+nNY8VWsadxPqqnzIpcU8UqrxIqJeVa+jbqbsAjsttJPJyACagp
// @require https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js#sha256-ofji4RA7ZKBd+T/Ij8gVhRYyfvqark3dhZ/wD+/wkPg=
// @namespace https://greasyfork.org/users/245195
// ==/UserScript==

/* jshint esversion: 6 */

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 4);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

module.exports = log;

/***/ }),
/* 1 */
/***/ (function(module, exports) {

module.exports = rxjs;

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = rxjs.operators;

/***/ }),
/* 3 */
/***/ (function(module, exports) {

module.exports = Swal;

/***/ }),
/* 4 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);

// EXTERNAL MODULE: external "log"
var external_log_ = __webpack_require__(0);

// EXTERNAL MODULE: external "Swal"
var external_Swal_ = __webpack_require__(3);
var external_Swal_default = /*#__PURE__*/__webpack_require__.n(external_Swal_);

// EXTERNAL MODULE: external "rxjs"
var external_rxjs_ = __webpack_require__(1);

// EXTERNAL MODULE: external "rxjs.operators"
var external_rxjs_operators_ = __webpack_require__(2);

// CONCATENATED MODULE: ./src/sort_component/index.ts


const createNumberInput = (value, min, max, step, width, ...className) => {
    const input = document.createElement('input');
    input.type = 'number';
    input.value = value.toString();
    input.min = min.toString();
    input.max = max.toString();
    input.step = step.toString();
    input.setAttribute('required', '');
    input.style.width = width;
    input.classList.add(...className);
    return input;
};
const createLabel = (text, ...className) => {
    const label = document.createElement('label');
    label.innerHTML = text;
    label.classList.add(...className);
    return label;
};
const createTextInput = (text, maxLength, size, ...className) => {
    const input = document.createElement('input');
    input.type = 'text';
    input.value = text;
    input.maxLength = maxLength;
    input.size = size;
    input.classList.add(...className);
    return input;
};
const createButton = (text, ...className) => {
    const button = document.createElement('button');
    button.innerHTML = text;
    button.classList.add(...className);
    return button;
};
/* harmony default export */ var sort_component = (class {
    constructor(initialSortValue, defaultSortValue, pageCount) {
        this.label1 = createLabel('', 'text-primary');
        this.loadedPageCount = 0;
        this.sortValueInput = createTextInput(initialSortValue, 120, 65, 'form-control', 'input-sm');
        this.sortButton = createButton('Sort', 'btn', 'btn-sm', 'btn-primary');
        this.sort$ = Object(external_rxjs_["fromEvent"])(this.sortButton, 'click').pipe(Object(external_rxjs_operators_["map"])(() => this.sortValueInput.value));
        this.sortValueInput.addEventListener('keyup', (event) => {
            if (event.key === 'Enter') {
                this.sortButton.click();
            }
        });
        const resetDefaultButton = createButton('Default', 'btn', 'btn-sm', 'btn-info');
        Object(external_rxjs_["fromEvent"])(resetDefaultButton, 'click').subscribe(() => {
            this.sortValueInput.value = defaultSortValue;
            GM.setValue('sortValue', defaultSortValue);
        });
        const label3 = createLabel('', 'text-primary');
        const pageCountInput = createNumberInput(pageCount, 1, 300, 1, '7rem', 'form-control', 'input-sm');
        pageCountInput.addEventListener('change', (event) => {
            GM.setValue('pageCount', Number.parseInt(event.target.value, 10));
            label3.innerHTML = 'Refresh to apply the change.';
        });
        const label2 = createLabel('pages loaded', 'text-primary');
        this.UI = document.createElement('div');
        this.UI.style.display = 'inline-block';
        this.UI.classList.add('form-inline', 'container');
        this.UI.append(this.sortValueInput, resetDefaultButton, this.sortButton, this.label1, pageCountInput, label2, label3);
        this.UI.childNodes.forEach((node) => {
            node.style.margin = '5px 2px';
        });
        this.loadedPageCount = 0;
        this.addLoadedPageCount();
    }
    sort() {
        this.sortButton.click();
    }
    addLoadedPageCount() {
        this.loadedPageCount += 1;
        this.label1.innerHTML = `${this.loadedPageCount} of `;
    }
});

// CONCATENATED MODULE: ./src/teaser_element_selector/index.ts
/* harmony default export */ var teaser_element_selector = ('.node-teaser, .node-sidebar_teaser, .node-wide_teaser');

// CONCATENATED MODULE: ./src/sort_teasers/index.ts


// eslint-disable-next-line no-new-func
const getTeaserValue = (item, expression) => new Function('views', 'likes', 'ratio', 'image', 'gallery', 'private', `return (${expression})`)(item.viewCount, item.likeCount, Math.min(item.likeCount / Math.max(1, item.viewCount), 1), item.imageFactor, item.galleryFactor, item.privateFactor);
const sortContainer = (container, valueExpression) => {
    const viewsIconSelector = '.glyphicon-eye-open';
    const likesIconSelector = '.glyphicon-heart';
    const imageFieldSelector = '.field-type-image';
    const galleryIconSelector = '.glyphicon-th-large';
    const privateDivSelector = '.private-video';
    const teaserDivs = Array.from(container.querySelectorAll(teaser_element_selector));
    let sortedTeaserCount = 0;
    if (container.hasAttribute('data-sorted-teaser-count')) {
        sortedTeaserCount = parseInt(container.getAttribute('data-sorted-teaser-count'), 10);
    }
    teaserDivs.forEach((div) => {
        if (!div.hasAttribute('data-original-order')) {
            div.setAttribute('data-original-order', sortedTeaserCount.toString());
            sortedTeaserCount += 1;
        }
    });
    container.setAttribute('data-sorted-teaser-count', sortedTeaserCount.toString());
    const getNearbyNumber = (element) => {
        const parsePrefixed = (str) => Number.parseFloat(str) * (str.includes('k') ? 1000 : 1);
        return element ? parsePrefixed(element.nextSibling.wholeText.replace(/,/g, '')) : 0;
    };
    const teasers = teaserDivs.map((div) => ({
        domElement: div,
        viewCount: getNearbyNumber(div.querySelector(viewsIconSelector)),
        likeCount: getNearbyNumber(div.querySelector(likesIconSelector)),
        imageFactor: div.querySelector(imageFieldSelector) ? 1 : 0,
        galleryFactor: div.querySelector(galleryIconSelector) ? 1 : 0,
        privateFactor: div.querySelector(privateDivSelector) ? 1 : 0,
    }));
    const teaserValuePairs = teasers.map((teaser) => [
        teaser.domElement,
        getTeaserValue(teaser, valueExpression),
    ]);
    teaserValuePairs.sort((itemA, itemB) => itemB[1] - itemA[1]);
    teaserDivs.map((div) => {
        const anchor = document.createElement('span');
        div.before(anchor);
        return anchor;
    }).forEach((div, index) => div.replaceWith(teaserValuePairs[index][0]));
};
/* harmony default export */ var sort_teasers = ((containers, valueExpression) => {
    GM.setValue('sortValue', valueExpression);
    let sortedCount = 0;
    containers.forEach((grid) => {
        sortContainer(grid, valueExpression);
        sortedCount += 1;
    });
    external_log_["info"](`${sortedCount} grids sorted`);
});

// CONCATENATED MODULE: ./src/get_teaser_containers/index.ts

/* harmony default export */ var get_teaser_containers = ((node) => {
    const containerSelector = '.views-responsive-grid, .node-playlist .field-name-field-videos';
    return Array.from(node.querySelectorAll(containerSelector))
        .filter((grid) => Boolean(grid.querySelector(teaser_element_selector)));
});

// CONCATENATED MODULE: ./src/get_page_param/index.ts
const getNumberParam = (URL_, name) => {
    const param = URL_.searchParams.get(name);
    return param ? Number.parseInt(param, 10) : 0;
};
/* harmony default export */ var get_page_param = ((URL_) => getNumberParam(URL_, 'page'));

// CONCATENATED MODULE: ./src/init_parent/index.ts








const changePageParam = (anchor, value) => {
    const anchorURL = new URL(anchor.href, window.location.href);
    anchorURL.searchParams.set('page', value.toString());
    anchor.href = anchorURL.href;
};
const groupCurrentPageItems = (currentPageItems) => {
    const parentItem = document.createElement('li');
    currentPageItems[0].before(parentItem);
    currentPageItems[0].style.marginLeft = '0';
    const groupList = document.createElement('ul');
    groupList.style.display = 'inline';
    groupList.style.backgroundColor = 'hsla(0, 0%, 75%, 50%)';
    currentPageItems.forEach((item) => {
        item.classList.replace('pager-item', 'pager-current');
        groupList.append(item);
    });
    parentItem.append(groupList);
};
const adjustPageAnchors = (container, pageCount) => {
    const currentPage = get_page_param(new URL(window.location.href));
    if (currentPage > 0) {
        const previousPageAnchor = container.querySelector('.pager-previous a');
        changePageParam(previousPageAnchor, Math.max(0, currentPage - pageCount));
    }
    const nextPage = currentPage + pageCount;
    {
        const lastPageAnchor = container.querySelector('.pager-last a');
        const nextPageAnchor = container.querySelector('.pager-next a');
        if (nextPageAnchor) {
            changePageParam(nextPageAnchor, nextPage);
        }
        if (lastPageAnchor
            && get_page_param(new URL(lastPageAnchor.href, window.location.href)) < nextPage) {
            nextPageAnchor.remove();
            lastPageAnchor.remove();
        }
    }
    const currentPageAnchors = Array.from(container.querySelectorAll('.pager-item a'))
        .filter((anchor) => {
        const page = get_page_param(new URL(anchor.href, window.location.href));
        return page >= currentPage && page < nextPage;
    });
    if (currentPageAnchors.length > 0) {
        const currentPageItems = [
            container.querySelector('.pager-current'),
            ...currentPageAnchors.map((anchor) => anchor.parentElement),
        ];
        groupCurrentPageItems(currentPageItems);
    }
};
const fixImages = () => {
    const brokenImages = get_teaser_containers(document).flatMap((container) => Array.from(container.querySelectorAll('img'))).filter((img) => img.complete && img.naturalWidth === 0);
    brokenImages.forEach((img) => {
        img.src = img.src;
        external_log_["info"]('Reload a broken image');
    });
};
/* harmony default export */ var init_parent = (async (teasersAddedMeesage) => {
    const defaultSortValue = '(ratio / (private * 2.0 + 1) + Math.log(likes) / 250) / (image + 8.0)';
    const initialSortValue = await GM.getValue('sortValue', defaultSortValue);
    const pageCount = await GM.getValue('pageCount', 1);
    const sortComponent = new sort_component(initialSortValue, defaultSortValue, pageCount);
    document.querySelector('#user-links').after(sortComponent.UI);
    if (get_teaser_containers(document).length === 0) {
        return;
    }
    let pages = [];
    sortComponent.sort$.subscribe((sortValue) => {
        try {
            sort_teasers(get_teaser_containers(document), sortValue);
        }
        catch (error) {
            external_Swal_default.a.fire('Sorting Failed', `An error accured while sorting: ${error.toString()}`);
            pages.forEach((page) => {
                page.src = '';
                page.remove();
            });
            pages = [];
        }
    });
    let imageToFix$ = Object(external_rxjs_["merge"])(Object(external_rxjs_["of"])(0), sortComponent.sort$.pipe(Object(external_rxjs_operators_["mapTo"])(0)));
    if (document.querySelector('.pager') && !document.querySelector('#comments')) {
        {
            const pageURL = new URL(window.location.href);
            const params = pageURL.searchParams;
            let page = get_page_param(pageURL);
            for (let pageLeft = pageCount - 1; pageLeft > 0; pageLeft -= 1) {
                page += 1;
                params.set('page', page.toString());
                const nextPage = (() => (document.createElement(navigator.userAgent.indexOf('Firefox') > -1 ? 'embed' : 'iframe')))();
                nextPage.src = pageURL.toString();
                nextPage.style.display = 'none';
                pages.push(nextPage);
                external_log_["info"]('Add page:', nextPage.src);
            }
        }
        document.body.append(...pages);
        external_log_["debug"](pages);
        document.querySelectorAll('.pager').forEach((list) => adjustPageAnchors(list, pageCount));
        const addTeasers$ = Object(external_rxjs_["fromEvent"])(window, 'message').pipe(Object(external_rxjs_operators_["filter"])((event) => (new URL(event.origin).hostname === window.location.hostname
            && event.data === teasersAddedMeesage
            && pages.length > 0)));
        addTeasers$.subscribe((event) => {
            sortComponent.sort();
            sortComponent.addLoadedPageCount();
            const loadedPage = event.source
                .frameElement;
            loadedPage.src = '';
            loadedPage.remove();
        });
        imageToFix$ = Object(external_rxjs_["merge"])(imageToFix$, addTeasers$.pipe(Object(external_rxjs_operators_["mapTo"])(0)));
    }
    sortComponent.sort();
    imageToFix$.pipe(Object(external_rxjs_operators_["mergeMap"])(() => Object(external_rxjs_["timer"])(0, 8000).pipe(Object(external_rxjs_operators_["take"])(2))), Object(external_rxjs_operators_["auditTime"])(6000)).subscribe(() => fixImages());
});

// CONCATENATED MODULE: ./src/timeout/index.ts
/* harmony default export */ var timeout = ((delay) => new Promise((resolve) => {
    setTimeout(resolve, delay);
}));

// CONCATENATED MODULE: ./src/init_child/index.ts


/* harmony default export */ var init_child = (async (teasersAddedMeesage) => {
    const teaserGrids = get_teaser_containers(document);
    if (teaserGrids.length === 0) {
        return;
    }
    await timeout(500);
    const parentGrids = get_teaser_containers(window.parent.document);
    for (let i = 0, j = 0; i < parentGrids.length; i += 1) {
        if (teaserGrids[j].className === parentGrids[i].className) {
            teaserGrids[j].className = '';
            parentGrids[i].prepend(teaserGrids[j]);
            j += 1;
        }
    }
    window.parent.postMessage(teasersAddedMeesage, window.location.origin);
});

// CONCATENATED MODULE: ./src/index.ts



const initialize = async () => {
    const isParent = (window === window.parent);
    external_log_["debug"](`isParent: ${isParent}.`, window.location);
    const teasersAddedMeesage = 'iwara custom sort: teasersAdded';
    if (isParent) {
        await init_parent(teasersAddedMeesage);
    }
    else {
        await init_child(teasersAddedMeesage);
    }
};
(async () => {
    try {
        external_log_["setLevel"]('trace');
        external_log_["debug"](`Parsed: ${document.readyState}`);
        await initialize();
    }
    catch (error) {
        external_log_["trace"](error);
    }
})();


/***/ })
/******/ ]);