// ==UserScript==
// @name Iwara Custom Sort
// @version 0.143
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @run-at document-start
// @noframes
// @match https://ecchi.iwara.tv/*
// @match https://www.iwara.tv/*
// @match http://ecchi.iwara.tv/*
// @match http://www.iwara.tv/*
// @description Automatically sort video results in a page on /videos, /images, /subscriptions, /users, and sidebars using customizable sort function.
// @namespace https://greasyfork.org/users/245195
// ==/UserScript==
/* jshint esversion: 6 */
/* global GM */
'use strict';
const additionalPageCount = 0;
const logDebug = (...args) => {
const debugging = true;
if (debugging) {
console.log(...args);
}
};
const timeout = delay => new Promise(resolve => setTimeout(resolve, delay));
const parsePrefixed = str => Number.parseFloat(str) * (str.includes('k') ? 1000 : 1);
const getNearbyNumber = element => (element ? parsePrefixed(element.parentElement.textContent) : 0);
const evalSortValue = (item, valueExpression) =>
// eslint-disable-next-line no-new-func
new Function('views', 'likes', 'ratio', 'image', 'private', `return (${valueExpression})`)(
item.viewCount,
item.likeCount,
Math.min(item.likeCount / Math.max(1, item.viewCount), 1),
item.imageFactor,
item.privateFactor,
);
const sortVideos = (container, valueExpression) => {
const videoDivs = Array.from(container.querySelectorAll('.clearfix'));
const videoItems = videoDivs
.map(div => ({
div,
viewCount: getNearbyNumber(div.querySelector('.glyphicon-eye-open')),
likeCount: getNearbyNumber(div.querySelector('.glyphicon-heart')),
imageFactor: div.querySelector('.glyphicon-th-large') ? 1 : 0,
privateFactor: div.querySelector('.private-video') ? 1 : 0,
}))
.sort((itemA, itemB) =>
evalSortValue(itemB, valueExpression) - evalSortValue(itemA, valueExpression));
videoDivs
.map((div) => {
const anchor = document.createElement('div');
div.before(anchor);
return anchor;
})
.forEach((div, index) => div.replaceWith(videoItems[index].div));
};
const sortAllVideos = (valueExpression) => {
const containers = Array.from(document.querySelectorAll('.views-responsive-grid'));
GM.setValue('sortValue', valueExpression);
let sortedCount = 0;
try {
containers.forEach((container) => {
sortVideos(container, valueExpression);
sortedCount += 1;
});
} catch (message) {
alert(message);
}
logDebug(`${sortedCount} containers sorted at ${window.location}`);
};
const addPageEmbeds = (URL, pageCount) => {
logDebug('page');
const params = URL.searchParams;
let page = params.has('page') ? Number.parseInt(params.get('page')) : 0;
for (let pageLeft = pageCount; pageLeft > 0; pageLeft -= 1) {
page += 1;
params.set('page', page);
const nextPage = document.createElement('embed');
nextPage.src = URL;
nextPage.style.display = 'none';
logDebug('page', nextPage.src, pageLeft);
document.documentElement.append(nextPage);
}
};
const createUI = async () => {
const sortValueInput = document.createElement('input');
sortValueInput.maxLength = 120;
sortValueInput.size = 60;
const defaultValue = '(ratio / (private * 2.5 + 1) + Math.sqrt(likes) / 3000) / (image + 3)';
sortValueInput.value = await GM.getValue('sortValue', defaultValue);
const sortButton = document.createElement('button');
sortButton.innerHTML = 'Sort';
sortValueInput.addEventListener('keyup', (event) => {
if (event.key !== 'Enter') {
return;
}
sortButton.click();
event.preventDefault();
});
sortButton.addEventListener('click', () => sortAllVideos(sortValueInput.value));
const resetDefaultButton = document.createElement('button');
resetDefaultButton.innerHTML = 'Default';
resetDefaultButton.addEventListener('click', () => {
sortValueInput.value = defaultValue;
});
return {
sortValueInput,
sortButton,
resetDefaultButton,
};
};
const addUI = (UI) => {
const UIDiv = document.createElement('div');
UIDiv.style.display = 'inline';
UIDiv.style.margin = '5px';
UIDiv.append(UI.resetDefaultButton, UI.sortValueInput, UI.sortButton);
document.querySelector('#user-links')
.prepend(UIDiv);
};
const addVideosToParent = (videoContainers) => {
const parentContainers = window.parent.document.querySelectorAll('.views-responsive-grid');
videoContainers.forEach((container, index) => {
// eslint-disable-next-line no-param-reassign
container.className = '';
if (parentContainers.length > index) {
parentContainers[index].prepend(container);
}
});
window.parent.postMessage({
sender: 'iwara custom sort',
message: 'videosAdded',
}, window.location.origin);
};
const init = async () => {
const UI = await createUI();
const onMessageParent = (event) => {
const originURL = new URL(event.origin);
if (
originURL.hostname === window.location.hostname &&
event.data.sender === 'iwara custom sort' &&
event.data.message === 'videosAdded'
) {
sortAllVideos(UI.sortValueInput.value);
}
};
const initParent = () => {
addUI(UI);
window.addEventListener('message', onMessageParent);
sortAllVideos(UI.sortValueInput.value);
if (/\/(videos|images|subscriptions)$/.test(window.location.pathname)) {
addPageEmbeds(new URL(window.location), additionalPageCount);
}
};
const onDOMContentLoaded = async () => {
const videoContainers = Array.from(document.querySelectorAll('.views-responsive-grid'));
if (window === window.parent) {
initParent();
} else if (videoContainers.length > 0) {
await timeout(500);
addVideosToParent(videoContainers);
}
};
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
};
(() => {
logDebug(`Parsed:${window.location}, ${document.readyState} Parent:`, window.parent);
if (window !== window.parent) {
logDebug('I am a child.');
}
init();
})();
/*
parsePrefixed = str => Number.parseFloat(str) * (str.includes('k') ? 1000 : 1);
getNearbyNumber = element => (element ? parsePrefixed(element.parentElement.textContent) : 0);
videoDivs = Array.from(document.querySelector('.views-responsive-grid').querySelectorAll('.clearfix'));
accFunc = (items) => {
console.log(items.reduce((acc, value) => acc + value));
const sums = [];
for (let i = 0; i < items.length / 16; i += 1) {
let sum = 0;
for (let j = 0; j < 16; j += 1) {
sum += items[i * 16 + j];
}
sums.push(sum);
}
return sums;
}
videoItems = videoDivs
.map(div => getNearbyNumber(div.querySelector('.glyphicon-eye-open')));
console.log(accFunc(videoItems));
videoItems = videoDivs
.map(div => div.querySelector('.glyphicon-th-large') ? 1 : 0);
console.log(accFunc(videoItems));
videoItems = videoDivs
.map(div => div.querySelector('.private-video') ? 1 : 0);
console.log(accFunc(videoItems));
videoItems = videoDivs
.map(div => getNearbyNumber(div.querySelector('.glyphicon-heart')) / getNearbyNumber(div.querySelector('.glyphicon-eye-open')));
console.log(accFunc(videoItems));
*/