Ehentai Onekey Add Favorites

onekey add favorites

// ==UserScript==
// @name         E绅士一键添加收藏
// @name:en      Ehentai Onekey Add Favorites
// @namespace    https://greasyfork.org/zh-CN/users/51670-ruaruarua
// @version      0.3.1
// @description  一键添加收藏9,再也不用看烦人的弹窗啦
// @description:en onekey add favorites
// @author       ruaruarua
// @match        https://exhentai.org/*
// @match        https://e-hentai.org/*
// @icon         https://exhentai.org/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// ==/UserScript==

(async function () {
  'use strict';
  const DEFAULT_FAV = 9;
  let favcat = [];

  const addStyle = (() => {
    const style = {
      gallery:
        '.fav_gallery{width: 20px;position: relative;top: -22px;}#gdf#gdf{padding-top: 0px;padding-left: 0px;margin-top: 20px;margin-left: 30px;cursor: pointer;}',
      m: 'td#fav_td{width: 20px;padding: 0px 5px}.fav_m{width: 20px;padding: 0px;margin: 0px}',
      p: 'td#fav_td{width: 20px;padding: 0px 5px}.fav_p,.fav_m{width: 20px;padding: 0px;margin: 0px}',
      l: '.fav_l{top: -4px;width: 20px;padding: 0px;position: absolute;}',
      e: '.fav_e{left: 22px;top: 150px;width: 20px;padding: 0px;position: absolute;}',
      t: '.fav_t{width: 20px;position: absolute;top: 19px;left: -22px;}',
    };
    return (favType) => {
      const favStyle = document.createElement('style');
      favStyle.innerHTML = style[favType];
      document.head.appendChild(favStyle);
    };
  })();

  const handleFav = async (favUrl, isAddFav, favcat) => {
    const res = await fetch(favUrl, {
      method: 'POST',
      headers: {
        Referer: favUrl,
        Origin: location.origin,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: isAddFav
        ? `favcat=${favcat}&favnote=&apply=Add+to+Favorites&update=1`
        : 'favcat=favdel&favnote=&update=1',
      credentials: 'same-origin',
      referrer: favUrl,
    });
    if (!res.ok) throw new Error('Fail to add favorites.');

    const html = await res.text();
    let updateStyle = [
      ...html.matchAll(/<script type="text\/javascript">([\s\S]*?)<\/script>/g),
    ][1][1];
    updateStyle = updateStyle.replace(/window\.opener\./g, '').replace(/window\.close\(\);/, '');
    eval(updateStyle);
  };

  const resetOnclick = (e, favUrl, isAddFn, favcat) => {
    e.removeAttribute('onclick');
    e.addEventListener('click', () => {
      handleFav(favUrl, isAddFn(), favcat);
    });
  };

  const createFavSelect = (favUrl, favClass) => {
    const select = document.createElement('select');
    select.innerHTML = '<option value="" disabled selected hidden></option>';
    favcat.forEach(
      (favname, idx) => (select.innerHTML += `<option value="${idx}">${favname}</option>`)
    );

    select.className = favClass;
    select.addEventListener('change', (event) => {
      handleFav(favUrl, true, event.target.value);
    });

    return select;
  };

  const updateFavcat = async () => {
    try {
      const res = await fetch(location.origin + '/uconfig.php');
      if (!res.ok) throw new Error('can not fetch /uconfig.php');
      const html = await res.text();
      // 未登录则返回空数组;
      const matchFavcat = [...html.matchAll(/input type="text" name="favorite_\d" value="(.*?)"/g)];
      const favcat = matchFavcat.map((arr) => arr[1]);
      localStorage.favcat = JSON.stringify(favcat);
    } catch (error) {
      console.log(error);
      if (localStorage.favcat) return;
      const favcat = Array.from({ length: 10 }).map((_, idx) => 'Favorites ' + idx);
      localStorage.favcat = JSON.stringify(favcat);
    }
  };

  GM_registerMenuCommand('update favcat', updateFavcat);
  if (!localStorage.favcat) await updateFavcat();
  favcat = JSON.parse(localStorage.favcat);

  const matchGallery = location.pathname.match(/\/g\/(\d+)\/(\w+)/);
  if (matchGallery) {
    const favUrl = `${location.origin}/gallerypopups.php?gid=${matchGallery[1]}&t=${matchGallery[2]}&act=addfav`;
    const favoritelink = document.querySelector('#favoritelink');
    const gdf = document.querySelector('#gdf');

    addStyle('gallery');
    resetOnclick(gdf, favUrl, () => favoritelink.innerText === ' Add to Favorites', DEFAULT_FAV);
    gdf.parentElement.appendChild(createFavSelect(favUrl, 'fav_gallery'));
    document.body.addEventListener('keydown', (e) => {
      if (e.key === 'q' && !e.repeat) {
        handleFav(favUrl, true, DEFAULT_FAV);
      }
    });
  } else {
    const dms = document.querySelector('.searchnav div:last-child select');
    if (!dms) return;

    const displayMode = dms.value;
    const styleStrategy = {
      m: {
        selector: '.glthumb + div',
        addSelect(e, favUrl) {
          const thead = document.querySelector('table.itg tr');
          thead.insertBefore(document.createElement('th'), thead.firstChild);

          const newAddSelect = (ne, nfavUrl) => {
            const td = ne.parentElement.parentElement.insertBefore(
              document.createElement('td'),
              ne.parentElement
            );
            td.id = 'fav_td';
            td.appendChild(createFavSelect(nfavUrl, 'fav_m'));
          };
          newAddSelect(e, favUrl);
          this.addSelect = newAddSelect;
        },
      },
      p: {
        selector: '.glthumb + div',
        addSelect(e, favUrl) {
          styleStrategy.m.addSelect.call(this, e, favUrl);
        },
      },
      l: {
        selector: '.glthumb + div > :first-child',
        addSelect(e, favUrl) {
          e.style = 'left: 23px';
          e.parentElement.appendChild(createFavSelect(favUrl, 'fav_l'));
        },
      },
      e: {
        selector: '.gl3e>:nth-child(2)',
        addSelect(e, favUrl) {
          e.parentElement.appendChild(createFavSelect(favUrl, 'fav_e'));
        },
      },
      t: {
        selector: '.gl5t>:first-child>:nth-child(2)',
        addSelect(e, favUrl) {
          e.parentElement.appendChild(createFavSelect(favUrl, 'fav_t'));
        },
      },
    };

    addStyle(displayMode);
    const strategy = styleStrategy[displayMode];

    document.querySelectorAll(strategy.selector).forEach((e) => {
      const favUrl = e.onclick.toString().match(/https.*addfav/)[0];
      resetOnclick(e, favUrl, () => !e.hasAttribute('title'), DEFAULT_FAV);
      strategy.addSelect(e, favUrl);
    });
  }
})();