// ==UserScript==
// @name Iwara Custom Sort
// @name:ja Iwara Custom ソート
// @version 0.199
// @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, and sidebars using customizable sort function. Can load and sort multiple pages at once.
// @description:ja / videos、/ images、/ subscriptions、/ usersとサイドバーのサムネイルを自動的にソートします。ソート方法はカスタマイズすることができます、一度に複数のページを読み込んでソートすることができます。
// @license AGPL-3.0-or-later
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js#sha256-4/EaXPJ/6N3TkeW1FnAqmfV7JNVmnIFQ3bllkklPJ9U=
// @require https://unpkg.com/[email protected]/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 = Swal;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = rxjs.operators;
/***/ }),
/* 4 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// EXTERNAL MODULE: external "Swal"
var external_Swal_ = __webpack_require__(2);
var external_Swal_default = /*#__PURE__*/__webpack_require__.n(external_Swal_);
// EXTERNAL MODULE: external "log"
var external_log_ = __webpack_require__(0);
// CONCATENATED MODULE: ./src/timeout/index.ts
/* harmony default export */ var timeout = ((delay) => new Promise((resolve) => {
setTimeout(resolve, delay);
}));
// EXTERNAL MODULE: external "rxjs"
var external_rxjs_ = __webpack_require__(1);
// EXTERNAL MODULE: external "rxjs.operators"
var external_rxjs_operators_ = __webpack_require__(3);
// CONCATENATED MODULE: ./src/sort_component/index.ts
const createNumberInput = (value, min, max, step, width) => {
const input = document.createElement('input');
input.value = value.toString();
input.min = min.toString();
input.max = max.toString();
input.step = step.toString();
input.setAttribute('required', '');
input.style.width = width;
return input;
};
const createSpan = (text, color) => {
const span = document.createElement('span');
span.innerHTML = text;
span.style.color = color;
return span;
};
const createTextInput = (text, maxLength, size) => {
const input = document.createElement('input');
input.value = text;
input.maxLength = maxLength;
input.size = size;
return input;
};
const createButton = (text) => {
const button = document.createElement('button');
button.innerHTML = text;
return button;
};
/* harmony default export */ var sort_component = (class {
constructor(initialSortValue, defaultSortValue, pageCount) {
this.lable1 = createSpan('', 'white');
this.loadedPageCount = 0;
const pageCountInput = createNumberInput(pageCount, 1, 15, 1, '3em');
const lable2 = createSpan('pages loaded.', 'white');
pageCountInput.addEventListener('change', (event) => {
GM.setValue('pageCount', Number.parseInt(event.target.value, 10));
lable2.innerHTML = 'pages loaded. Refresh to apply the change';
});
this.sortValueInput = createTextInput(initialSortValue, 120, 60);
this.sortValueInput.classList.add('form-text');
this.sortButton = createButton('Sort');
this.sort$ = Object(external_rxjs_["fromEvent"])(this.sortButton, 'click').pipe(Object(external_rxjs_operators_["map"])(() => this.sortValueInput.value));
this.loadedPageCount = 0;
this.sortButton.classList.add('btn', 'btn-sm', 'btn-primary');
this.sortValueInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter') {
this.sortButton.click();
}
});
const resetDefaultButton = createButton('Default');
Object(external_rxjs_["fromEvent"])(resetDefaultButton, 'click').subscribe(() => {
this.sortValueInput.value = defaultSortValue;
GM.setValue('sortValue', defaultSortValue);
});
resetDefaultButton.classList.add('btn', 'btn-sm', 'btn-info');
this.UI = document.createElement('div');
this.UI.style.display = 'inline-block';
this.UI.append(this.sortValueInput, resetDefaultButton, this.sortButton, this.lable1, pageCountInput, lable2);
this.UI.childNodes.forEach((node) => {
node.style.margin = '5px 2px';
});
this.addLoadedPageCount();
}
sort() {
this.sortButton.click();
}
addLoadedPageCount() {
this.loadedPageCount += 1;
this.lable1.innerHTML = `${this.loadedPageCount} of `;
}
});
// CONCATENATED MODULE: ./src/index.ts
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const teaserDivSelector = '.node-teaser, .node-sidebar_teaser';
const getTeaserGrids = (node) => {
const teaserGridSelector = '.views-responsive-grid';
return Array.from(node.querySelectorAll(teaserGridSelector))
.filter((grid) => grid.querySelector(teaserDivSelector));
};
const sortTeasers = (grid, 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(grid.querySelectorAll(teaserDivSelector));
const getNearbyNumber = (element) => {
const parsePrefixed = (str) => Number.parseFloat(str) * (str.includes('k') ? 1000 : 1);
return element ? parsePrefixed(element.parentElement.textContent) : 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,
}));
// eslint-disable-next-line no-new-func
const evalSortValue = (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 teaserPairs = teasers.map((teaser) => [teaser, evalSortValue(teaser, valueExpression)]);
teaserPairs.sort((itemA, itemB) => itemB[1] - itemA[1]);
teaserDivs.map((div) => {
const anchor = document.createElement('div');
div.before(anchor);
return anchor;
}).forEach((div, index) => div.replaceWith(teaserPairs[index][0].domElement));
};
const sortAllTeasers = (valueExpression) => {
GM.setValue('sortValue', valueExpression);
let sortedCount = 0;
getTeaserGrids(document).forEach((grid) => {
sortTeasers(grid, valueExpression);
sortedCount += 1;
});
external_log_["info"](`${sortedCount} grids sorted`);
};
const getPageParam = (URL_) => {
const params = URL_.searchParams;
return params.has('page') ? Number.parseInt(params.get('page'), 10) : 0;
};
const currentPage = getPageParam(new URL(window.location.href));
const createAdditionalPages = (URL_, pageCount) => {
const params = URL_.searchParams;
let page = getPageParam(URL_);
const pages = [];
for (let pageLeft = pageCount; pageLeft > 0; pageLeft -= 1) {
page += 1;
params.set('page', page.toString());
const nextPage = (() => (navigator.userAgent.indexOf('Firefox') > -1
? document.createElement('embed')
: document.createElement('iframe')))();
nextPage.src = URL_.toString();
nextPage.style.display = 'none';
external_log_["info"]('Add page:', nextPage.src);
pages.push(nextPage);
}
return pages;
};
const addTeasersToParent = (teaserGrids) => {
const parentGrids = getTeaserGrids(window.parent.document);
for (let i = 0, j = 0; i < parentGrids.length; i += 1) {
if (teaserGrids[j].className === parentGrids[i].className) {
teaserGrids[j].className = '';
teaserGrids[j].setAttribute('originPage', currentPage.toString());
parentGrids[i].prepend(teaserGrids[j]);
j += 1;
}
}
};
const adjustPageAnchors = (container, pageCount) => {
const changePageParam = (anchor, value) => {
const anchorURL = new URL(anchor.href, window.location.href);
anchorURL.searchParams.set('page', value.toString());
anchor.href = anchorURL.pathname + anchorURL.search;
};
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) {
if (getPageParam(new URL(lastPageAnchor.href, window.location.href)) < nextPage) {
nextPageAnchor.remove();
lastPageAnchor.remove();
}
}
}
const loadedPageAnchors = Array.from(container.querySelectorAll('.pager-item a'))
.filter((anchor) => {
const page = getPageParam(new URL(anchor.href, window.location.href));
return page >= currentPage && page < nextPage;
});
if (loadedPageAnchors.length > 0) {
const parentItem = document.createElement('li');
const groupList = document.createElement('ul');
groupList.style.display = 'inline';
groupList.style.backgroundColor = 'hsla(0, 0%, 75%, 50%)';
loadedPageAnchors[0].parentElement.before(parentItem);
const currentPageItem = container.querySelector('.pager-current');
currentPageItem.style.marginLeft = '0';
groupList.append(currentPageItem);
loadedPageAnchors.forEach((anchor) => {
anchor.parentNode.classList.remove('pager-item');
anchor.parentNode.classList.add('pager-current');
groupList.append(anchor.parentElement);
});
parentItem.append(groupList);
}
};
const adjustAnchors = (pageCount) => {
const pageAnchorList = document.querySelectorAll('.pager');
pageAnchorList.forEach((list) => {
adjustPageAnchors(list, pageCount);
});
};
const initParent = (teasersAddedMeesage) => __awaiter(undefined, void 0, void 0, function* () {
const pageCount = yield GM.getValue('pageCount', 1);
const defaultSortValue = '(ratio / (private * 2.0 + 1) + Math.log(likes) / 250) / (image + 8.0)';
const initialSortValue = yield GM.getValue('sortValue', defaultSortValue);
const sortComponent = new sort_component(initialSortValue, defaultSortValue, pageCount);
let pages = [];
sortComponent.sort$.subscribe((sortValue) => {
try {
sortAllTeasers(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 = [];
}
});
document.querySelector('#user-links').after(sortComponent.UI);
if (document.querySelectorAll('.pager').length > 0
&& document.querySelectorAll('#comments').length === 0) {
pages = createAdditionalPages(new URL(window.location.href), pageCount - 1);
document.body.append(...pages);
external_log_["info"](pages);
adjustAnchors(pageCount);
window.addEventListener('message', (event) => {
if (new URL(event.origin).hostname === window.location.hostname
&& event.data === teasersAddedMeesage
&& event.source !== null
&& pages.length > 0) {
try {
sortComponent.sort();
sortComponent.addLoadedPageCount();
external_log_["trace"](event.source.location);
const loadedPage = pages[getPageParam(new URL(event.source.location.href)) - currentPage - 1];
loadedPage.src = '';
loadedPage.remove();
}
catch (error) {
external_log_["trace"](error);
}
}
});
}
sortComponent.sort();
});
const initialize = () => __awaiter(undefined, void 0, void 0, function* () {
try {
external_log_["setLevel"]('trace');
external_log_["debug"](`Parsed:${window.location}, ${document.readyState} Parent:`, window.parent);
const teaserGrids = getTeaserGrids(document);
if (teaserGrids.length === 0) {
return;
}
const teasersAddedMeesage = 'iwara custom sort: teasersAdded';
if (window === window.parent) {
external_log_["debug"]('I am a Parent.');
initParent(teasersAddedMeesage);
}
else {
external_log_["debug"]('I am a child.', window.location, window.parent.location);
yield timeout(500);
addTeasersToParent(teaserGrids);
window.parent.postMessage(teasersAddedMeesage, window.location.origin);
}
}
catch (error) {
external_log_["trace"](error);
}
});
initialize();
/***/ })
/******/ ]);