// ==UserScript==
// @name Iwara Custom Sort
// @name:ja Iwara Custom ソート
// @version 0.209
// @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/*
// eslint-disable-next-line max-len
// @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.
// eslint-disable-next-line max-len
// @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.operators;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = rxjs;
/***/ }),
/* 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__(2);
// EXTERNAL MODULE: external "rxjs.operators"
var external_rxjs_operators_ = __webpack_require__(1);
// 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', async (event) => {
await 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;
}
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
const getTeaserValue = (info, expression) => {
const getSortParamPairs = [
['index', info.initialIndex],
['views', info.viewCount],
['likes', info.likeCount],
['ratio', Math.min(info.likeCount / Math.max(1, info.viewCount), 1)],
['image', info.imageFactor],
['gallery', info.galleryFactor],
['private', info.privateFactor],
];
// eslint-disable-next-line no-new-func
return new Function(...getSortParamPairs.map(([name]) => name), `return (${expression})`)(...getSortParamPairs.map((pairs) => pairs[1]));
};
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));
const sortedTeaserCount = container.dataset.sortedTeaserCount
? parseInt(container.dataset.sortedTeaserCount, 10) : 0;
teaserDivs.filter((div) => !div.dataset.initialIndex)
.forEach((div, index) => {
div.dataset.initialIndex = (sortedTeaserCount + index).toString();
});
container.dataset.sortedTeaserCount = teaserDivs.length.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 teaserInfos = teaserDivs.map((div) => ({
initialIndex: parseInt(div.dataset.initialIndex, 10),
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 divValuePairs = teaserInfos.map((info, index) => [
teaserDivs[index],
getTeaserValue(info, valueExpression),
]);
divValuePairs.sort((itemA, itemB) => itemB[1] - itemA[1]);
teaserDivs.forEach((div) => div.after(document.createElement('span')));
teaserDivs.map((div) => div.nextSibling).forEach((anchor, index) => anchor.replaceWith(divValuePairs[index][0]));
};
/* harmony default export */ var sort_teasers = ((containers, valueExpression) => {
GM.setValue('sortValue', valueExpression);
containers.forEach((container) => {
sortContainer(container, valueExpression);
});
external_log_["info"](`${containers.length} 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/teaser_added_message/index.ts
/* harmony default export */ var teaser_added_message = ('iwara custom sort: teasersAdded');
// 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(({ parentElement }) => 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');
});
};
const createPages = (startURL, pageCount, userAgent) => {
const currentURL = startURL;
const params = currentURL.searchParams;
const firstPage = get_page_param(currentURL);
const pages = [];
for (let currentPage = firstPage + 1; currentPage < firstPage + pageCount; currentPage += 1) {
params.set('page', currentPage.toString());
const nextPage = (() => (document.createElement(userAgent.indexOf('Firefox') > -1 ? 'embed' : 'iframe')))();
nextPage.src = currentURL.toString();
nextPage.style.display = 'none';
pages.push(nextPage);
external_log_["info"]('Add page:', nextPage.src);
}
return pages;
};
/* harmony default export */ var init_parent = (async () => {
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;
}
const haveMorePages = document.querySelector('.pager') && !document.querySelector('#comments');
if (haveMorePages) {
document.querySelectorAll('.pager').forEach((list) => adjustPageAnchors(list, pageCount));
}
const pages = haveMorePages
? createPages(new URL(window.location.href), pageCount, window.navigator.userAgent) : [];
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();
});
}
});
const addPage$ = Object(external_rxjs_["fromEvent"])(window, 'message').pipe(Object(external_rxjs_operators_["filter"])((event) => (new URL(event.origin).hostname === window.location.hostname
&& event.data === teaser_added_message
&& pages.length > 0)), Object(external_rxjs_operators_["map"])((event) => event.source.frameElement));
addPage$.subscribe((element) => {
element.src = '';
element.remove();
});
const addTeasers$ = addPage$.pipe(Object(external_rxjs_operators_["mapTo"])(0), Object(external_rxjs_operators_["startWith"])(0));
addTeasers$.subscribe(() => {
sortComponent.sort();
sortComponent.addLoadedPageCount();
});
document.body.append(...pages);
external_log_["debug"](pages);
addTeasers$.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
const addContainersToParents = (children, parents) => {
const addContainerToParent = (child, parent) => {
child.className = '';
parent.prepend(child);
};
let i = 0;
parents.forEach((parent) => {
if (parent.className === children[i].className) {
addContainerToParent(children[i], parent);
i += 1;
}
});
};
/* harmony default export */ var init_child = (async () => {
const teaserContainers = get_teaser_containers(document);
if (teaserContainers.length === 0) {
return;
}
await timeout(500);
addContainersToParents(teaserContainers, get_teaser_containers(window.parent.document));
window.parent.postMessage(teaser_added_message, window.location.origin);
});
// CONCATENATED MODULE: ./src/initialize/index.ts
/* harmony default export */ var initialize = (async () => {
const isParent = (window === window.parent);
external_log_["debug"](`isParent: ${isParent}.`, window.location);
await (isParent ? init_parent() : init_child());
});
// CONCATENATED MODULE: ./src/index.ts
(async () => {
external_log_["setLevel"]('trace');
external_log_["debug"](`Parsed: ${document.readyState}`);
try {
await initialize();
}
catch (error) {
external_log_["error"](error);
}
})();
/***/ })
/******/ ]);