Iwara Custom Sort

Automatically sort video results in a page on /videos, /images, /subscriptions, /users, and sidebars using customizable sort function.

当前为 2019-02-13 提交的版本,查看 最新版本

// ==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));
*/