Sleazy Fork is available in English.

Download In JavLibrary

Download directly in javlibrary video detail page

// ==UserScript==
// @name                Download In JavLibrary
// @name:zh-CN          JavLibrary 下载
// @description         Download directly in javlibrary video detail page
// @description:zh-CN   直接在 javlibrary 影片详情页进行下载
// @version             0.3.0
// @author              jferroal
// @license             GPL-3.0
// @require             https://greasyfork.org/scripts/31793-jmul/code/JMUL.js?version=209567
// @include             http://www.javlibrary.com/*
// @grant               GM_xmlhttpRequest
// @run-at              document-end
// @namespace           https://greasyfork.org/users/34556
// ==/UserScript==

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
const { Element } = window.JMUL || { Element: {} };
const { ClickActionFactory } = require('./elements.logic');
const DEFAULT_ACTION_TYPE = 'unknown';

class PanelButton {
  constructor (type, label) {
    this.type = type;
    this.btn = new Element('button');
    this.btn.setInnerText(label);
    this.initStyle();
  }
  
  initStyle () {
    this.btn.setCss({
      width: '22px',
      height: '22px',
      boxSizing: 'border-box',
      marginLeft: '4px',
      cursor: 'pointer',
    });
  }
  
  bindClick (task) {
    const gfn = new (ClickActionFactory.create(this.type))(task).cb;
    this.btn.listen('click', (e) => gfn(e, task));
  }
  
  updateCss (styles) {
    this.btn.setCss(styles);
  }
  
  appendTo (parent) {
    return this.btn.appendTo(parent);
  }
}

class PanelButtonFactory {
  constructor () {
  }
  
  static create (type) {
    switch (type) {
      case 'active':
        return new PanelButton(type, '⏩');
      case 'waiting':
        return new PanelButton(type, '•');
      case 'Paused':
        return new PanelButton(type, '⏸');
      case 'Removed':
        return new PanelButton(type, '⌦');
      case 'Completed':
        return new PanelButton(type, '?');
      case 'Error':
        return new PanelButton(type, '❌');
      case 'unknown':
      default:
        const button = new PanelButton(type, '?');
        button.updateCss({
          color: 'white',
          backgroundColor: 'grey',
          borderRadius: '50%',
        });
        return button;
    }
  }
}

PanelButton.instance = undefined;

class Panel {
  constructor (task) {
    this.element = new Element('section');
    this.initStyles();
    this.initButton(task);
  }
  
  initStyles () {
    this.element.setCss({
      display: 'flex',
      margin: '-4px 0',
    });
  }
  
  initButton (task) {
    const button = PanelButtonFactory.create(DEFAULT_ACTION_TYPE);
    button.bindClick(task);
    button.appendTo(this.element);
  }
  
  appendTo (parent) {
    parent.setCss({
      display: 'flex',
      margin: '4px 15%',
    });
    parent.appendChild(this.element);
  }
}

module.exports = { Panel };

},{"./elements.logic":2}],2:[function(require,module,exports){
const { TokyoToSho, TaskPanel } = require('./requests');
const { Utils } = require('./utils');

class UnknownClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
      new TokyoToSho().search(this.task.name).then((response) => {
        const magnets = (new Utils.TokyoToShoParser(response.responseText)).matchAll();
        if (magnets && magnets.length) {
          this.task.chooseBestMagnet(magnets);
          new TaskPanel().start(this.task);
        } else {
          alert('无可用资源');
        }
      });
    }
  }
}

class ActiveClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class WaitingClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class PausedClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class RemovedClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class ErrorClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class CompletedClickAction {
  constructor (task) {
    this.task = task;
  }
  
  get cb () {
    return (event) => {
      event.preventDefault();
    }
  }
}

class ClickActionFactory {
  constructor () {
  }
  
  static create (type) {
    if (!ClickActionFactory.caches[ type ]) {
      switch (type) {
        case 'active':
          ClickActionFactory.caches[ type ] = ActiveClickAction;
          break;
        case 'waiting':
          ClickActionFactory.caches[ type ] = WaitingClickAction;
          break;
        case 'Paused':
          ClickActionFactory.caches[ type ] = PausedClickAction;
          break;
        case 'Removed':
          ClickActionFactory.caches[ type ] = RemovedClickAction;
          break;
        case 'Completed':
          ClickActionFactory.caches[ type ] = CompletedClickAction;
          break;
        case 'Error':
          ClickActionFactory.caches[ type ] = ErrorClickAction;
          break;
        case 'unknown':
        default:
          ClickActionFactory.caches[ type ] = UnknownClickAction;
      }
    }
    return ClickActionFactory.caches[ type ];
  }
}

ClickActionFactory.caches = {};

module.exports = { ClickActionFactory };

},{"./requests":4,"./utils":6}],3:[function(require,module,exports){
const { Utils } = require('./utils');
const { Panel } = require('./elements');

(function () {
  const href = window.location.href;
  if (/http:\/\/www\.javlibrary\.com\/.*\/\?v=.*/.test(href)) {
    const tasks = Utils.generateTasks(href);
    init(tasks);
    
    function init (tasks) {
      tasks.forEach((task) => {
        const statusBar = new Panel(task);
        statusBar.appendTo(task.panelParent);
      });
    }
  }
  
}());

},{"./elements":1,"./utils":6}],4:[function(require,module,exports){
// Search result from tokyotosho
const { Request, Header } = window.JMUL || { Request: {}, Header: {} };

class TokyoToSho {
  constructor (_options = {}) {
    if (!TokyoToSho.instance) {
      this.options = _options;
      this.initHeaders();
      this.host = 'https://www.tokyotosho.info';
      TokyoToSho.instance = this;
    }
    return TokyoToSho.instance;
  }
  
  initHeaders () {
    this.options.headers = new Header({
      ':authority': 'www.tokyotosho.info',
      ':scheme': 'https',
      'accept': 'text / html, application/xhtml+xml,application/xml;q=0.9, image/webp,*/*;q=0.8',
      'accept-encoding;': 'gzip, deflate, sdch, br',
      'accept-language': 'zh-CN, en-US;q=0.8, en;q=0.6, zh;q=0.4',
      'cache-control': 'no-cache',
    });
  }
  
  search (target) {
    const request = new Request(this.options);
    request.setMethod('GET');
    request.setUrl(this.host + `/search.php?terms=${target}`, ``);
    return request.send();
  }
}

TokyoToSho.instance = undefined;

class TaskPanel {
  constructor (_options = {}) {
    if (!TaskPanel.instance) {
      this.options = _options;
      this.initHeaders();
      this.host = 'http://localhost:6800/jsonrpc';
      TaskPanel.instance = this;
    }
    return TaskPanel.instance;
  }
  
  initHeaders () {
    this.options.headers = new Header({ 'Content-Type': 'application/json' });
  }
  
  start (task) {
    const request = new Request(this.options);
    request.setMethod('POST');
    request.setUrl(this.host);
    request.setData(task.generateRequestStr());
    return request.send();
  }
}

TaskPanel.instance = undefined;

module.exports = { TokyoToSho, TaskPanel };

},{}],5:[function(require,module,exports){
const { Element } = window.JMUL || { JMElement: {} };

class Task {
  constructor (name = '') {
    this.name = name;
  }
  
  setName (name) {
    this.name = name;
  }
  
  setPanelParent (el) {
    this.panelParent = new Element(el);
  }
  
  setProgressBarParent (el) {
    this.progressBarParent = new Element(el);
  }
  
  setMagnetLink (magnet) {
    this.magnet = magnet;
  }
  
  chooseBestMagnet (magnets) {
    this.setMagnetLink(magnets.reduce((best, magnet) => {
      const current = {
        link: magnet.link,
        score: (magnet.sCount || 0) * 10 + (magnet.cCount || 0) * 5 + (magnet.lCount || 0) * 2,
        size: parseInt(magnet.size.slice(0, -2), 10) * (magnet.size.indexOf('GB') > -1 ? 1000 : 1),
      };
      if (current.score < best.score) return best;
      if (current.score > best.score) return current;
      if (current.size < best.size) return best;
      return current;
    }, { link: '', score: 0, size: 0 }));
  }
  
  setServerStatus (serverTask) {
    this.completedLength = serverTask.completedLength;
    this.totalLength = serverTask.totalLength;
    this.status = serverTask.status;
  }
  
  generateRequestStr () {
    return JSON.stringify({
      jsonrpc: '2.0',
      id: this.name,
      method: 'aria2.addUri',
      params: [ [ this.magnet.link ] ],
    })
  }
  
  static joinName (tasks) {
    return tasks.reduce((res, t) => res += t.name + ';', '');
  }
  
  static fromSingleElem (elem) {
    const task = new Task();
    task.setName(elem.children[ 0 ].children[ 0 ].children[ 0 ].children[ 1 ].textContent);
    task.setPanelParent(elem);
    task.setProgressBarParent(elem.children[ 0 ].children[ 0 ].children[ 0 ].children[ 1 ]);
    return task;
  }
  
  static fromListElem (elem) {
    const task = new Task();
    task.setName(elem.children[ 0 ].children[ 0 ].textContent);
    task.setPanelParent(elem);
    task.setProgressBarParent(elem.children[ 0 ].children[ 0 ]);
    return task;
  }
  
  static fromHomeElem (elem) {
    const task = new Task();
    task.setName(elem.children[ 0 ].textContent);
    task.setPanelParent(elem);
    task.setProgressBarParent(elem.children[ 0 ]);
    return task;
  }
}

module.exports = { Task };

},{}],6:[function(require,module,exports){
const { Task } = require('./task');

function convertHTMLElementsToArray (elements) {
  const result = [];
  if (elements && elements.length) {
    for (let i = 0; i < elements.length; i += 1) {
      result.push(elements.item(i));
    }
  }
  return result;
}

const PageType = {
  SINGLE_VIEW: 100,
  VIDEO_LIST: 200,
  HOMEPAGE: 300,
};

class Utils {
  static pageType (href) {
    if (/http:\/\/www\.javlibrary\.com\/.*\/\?v=.*/.test(href)) {
      return PageType.SINGLE_VIEW;
    }
    if (/http:\/\/www\.javlibrary\.com\/.*\/vl_.*/.test(href)) {
      return PageType.VIDEO_LIST;
    }
    return PageType.HOMEPAGE;
  }
  
  static getTaskElements (type) {
    switch (type) {
      case PageType.SINGLE_VIEW:
        return [ document.getElementById('video_id') ];
      case PageType.VIDEO_LIST:
        return convertHTMLElementsToArray(document.getElementsByClassName('video'));
      case PageType.HOMEPAGE:
      default:
        return convertHTMLElementsToArray(document.getElementsByClassName('post-headline'));
    }
  }
  
  static generateTasks (href) {
    const type = Utils.pageType(href);
    const elements = Utils.getTaskElements(type);
    return elements.reduce((res, e) => {
      switch (type) {
        case PageType.SINGLE_VIEW:
          res.push(Task.fromSingleElem(e));
          break;
        case PageType.VIDEO_LIST:
          res.push(Task.fromListElem(e));
          break;
        case PageType.HOMEPAGE:
        default:
          res.push(Task.fromHomeElem(e));
          break;
      }
      return res;
    }, []);
  }
}

Utils.PageType = PageType;
Utils.TokyoToShoParser = class TokyoToShoParser {
  constructor (pageContent) {
    this.pageContent = pageContent;
    this.magnetLinkPattern = /<a href="(magnet:\?xt=urn:btih:.*?)">/gi;
    this.seederCountPattern = /S: <span style="color: .*?">(\d+)<\/span>/gi;
    this.leederCountPattern = /L: <span style="color: .*?">(\d+)<\/span>/gi;
    this.completedCountPattern = /C: <span style="color: .*?">(\d+)<\/span>/gi;
    this.sizePattern = /\| Size: (.*?) \|/gi;
  }
  
  matchAll () {
    const result = [];
    let [ mlMatch, scMatch, lcMatch, ccMatch, szMatch ] = [ undefined, undefined, undefined, undefined, undefined ];
    do {
      [ mlMatch, scMatch, lcMatch, ccMatch, szMatch ] = [
        this.magnetLinkPattern.exec(this.pageContent),
        this.seederCountPattern.exec(this.pageContent),
        this.leederCountPattern.exec(this.pageContent),
        this.completedCountPattern.exec(this.pageContent),
        this.sizePattern.exec(this.pageContent),
      ];
      if (mlMatch) {
        result.push({
          link: mlMatch[ 1 ].trim(),
          sCount: scMatch[ 1 ],
          lCount: lcMatch[ 1 ],
          cCount: ccMatch[ 1 ],
          size: (szMatch && szMatch[ 1 ]) || '0MB',
        });
      }
    } while (mlMatch);
    this.magnetLinkPattern.index = this.seederCountPattern.index = this.leederCountPattern.index = 0;
    this.completedCountPattern.index = this.sizePattern.index = 0;
    return result;
  }
};

module.exports = { Utils };

},{"./task":5}]},{},[3]);