AnilistBytes

adds torrents from animebytes to the anime view. Make sure to change the passkey and username from null or click the new button in the animebytes footer.

Instalar o script?
Script sugerido do autor

Você também pode gostar de SendToClient.

Instalar o script
// ==UserScript==
// @name        AnilistBytes
// @match       https://anilist.co/*
// @match       https://animebytes.tv/*
// @run-at      document-end
// @version     1.9.7
// @author      notmarek
// @icon        https://anilistbytes.notmarek.com/AB.svg
// @require     https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected]
// @description adds torrents from animebytes to the anime view. Make sure to change the passkey and username from null or click the new button in the animebytes footer.
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.xmlHttpRequest
// @namespace https://greasyfork.org/users/1095705
// ==/UserScript==

(function () {
'use strict';

var css_248z = "#footer_inner .footer_column{width:180px}.animebytes>p{display:grid;grid-auto-columns:1fr;grid-template-columns:1fr .5fr}h2.animebytes.stats{grid-column-gap:1rem;display:grid;grid-template-columns:1fr .5fr .5fr .5fr;text-align:center}";

let passkey = null; // You still can change this manually
let username = null; // Same here

// Get passkey and username from local storage

if (unsafeWindow.location.href.match(/animebytes\.tv/))
  // check which site we are on to run the correct script
  animebytes();else anilist();
document.head.append(VM.m(VM.h("style", null, css_248z)));
async function animebytes() {
  passkey = await GM.getValue('passkey', null);
  username = await GM.getValue('username', null);
  const save = async e => {
    e.preventDefault();
    let passkey = document.querySelector("link[type='application/rss+xml']").href.match(/\/feed\/rss_torrents_all\/(.*)/)[1];
    let username = document.querySelector('.username').innerText;
    await GM.setValue('passkey', passkey);
    await GM.setValue('username', username);
    alert('Passkey and username set you can now go to anilist!');
    return false;
  };
  let element = VM.h("div", null, VM.h("h3", null, "AnilistBytes"), VM.h("ul", {
    class: "nobullet"
  }, VM.h("li", null, VM.h("a", {
    href: "#",
    onclick: save,
    id: "anilistbytes"
  }, !passkey && !username ? 'Set Passkey & Username' : 'Update Passkey & Username'))));
  document.querySelector('#footer_inner').appendChild(VM.m(element));
}
async function getMALId(id, type, isAdult = false) {
  let query = {
    query: 'query media($id: Int, $type: MediaType, $isAdult: Boolean) { Media(id: $id, type: $type, isAdult: $isAdult) { idMal }}',
    variables: {
      id,
      type,
      isAdult
    }
  };
  let res = await fetch('https://anilist.co/graphql', {
    body: JSON.stringify(query),
    headers: {
      'content-type': 'application/json',
      'x-csrf-token': unsafeWindow.al_token
    },
    method: 'POST'
  });
  return (await res.json()).data.Media.idMal;
}
async function anilist() {
  passkey = await GM.getValue('passkey', null);
  username = await GM.getValue('username', null);
  if (passkey === null || username === null) {
    alert('Make sure to press the button in the footer of animebytes or edit the script to set your passkey and username!');
  }

  // stolen from https://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
  function formatBytes(a, b = 2) {
    if (!+a) return '0 Bytes';
    const c = 0 > b ? 0 : b,
      d = Math.floor(Math.log(a) / Math.log(1024));
    return `${parseFloat((a / Math.pow(1024, d)).toFixed(c))} ${['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'][d]}`;
  }
  const createTorrentEntry = (link, name, size, l, s, snatch, downMultipler) => {
    let st = VM.h(VM.Fragment, null, "\xA0|\xA0", VM.h("a", {
      style: "color:gray;",
      href: "",
      onclick: e => {
        unsafeWindow._addTo(link);
        e.target.innerText = 'Added!';
        return false;
      }
    }, "ST"));
    let flicon = VM.h("img", {
      src: "https://anilistbytes.notmarek.com/flicon.png",
      alt: "| Freeleech"
    });
    let sneedexicon = VM.h("img", {
      style: "margin-left: 5px;",
      src: "https://anilistbytes.notmarek.com/sndx.png",
      alt: "| Sneedex"
    });
    let anime = name.includes('| Freeleech') ? VM.h(VM.Fragment, null, name.replace('| Freeleech', ''), flicon) : VM.h(VM.Fragment, null, name);
    anime = sneedex.includes(link.match(/torrent\/(\d+)\/download/)[1]) ? VM.h(VM.Fragment, null, anime, sneedexicon) : VM.h(VM.Fragment, null, anime);
    return VM.h(VM.Fragment, null, VM.h("h2", null, VM.h("span", null, "[", VM.h("a", {
      href: link,
      style: "color:gray;"
    }, "\xA0DL"), unsafeWindow._addTo ? st : null, "\xA0]\xA0"), VM.h("span", null, anime)), VM.h("h2", {
      class: "animebytes stats"
    }, VM.h("span", null, formatBytes(size)), VM.h("span", null, String(snatch)), VM.h("span", null, String(s)), VM.h("span", null, String(l))));
  };

  // function to decode html entities in strings (e.g. & -> &)
  const getDecodedString = str => {
    const txt = document.createElement('textarea');
    txt.innerHTML = str;
    return txt.value;
  };

  // function using GM.xmlHttpRequest to make the xmlhttprequest closer to fetch
  const GM_get = async url => {
    return new Promise((resolve, reject) => {
      GM.xmlHttpRequest({
        method: 'GET',
        url,
        headers: {
          Accept: 'application/json'
        },
        onload: res => {
          resolve({
            json: async () => JSON.parse(res.responseText)
          });
        },
        onerror: err => {
          reject(err);
        },
        onabort: err => {
          reject(err);
        }
      });
    });
  };
  const cacheSneedex = async () => {
    let res = await GM_get('https://sneedex.moe/api/public/ab');
    let data = await res.json();
    data = data.map(e => e.permLinks.map(e => e.match(/torrentid=(\d+)/)[1])).flat();
    await GM.setValue('sneedexv2', data);
    return data;
  };
  let sneedex = await GM.getValue('sneedexv2', await cacheSneedex());
  const formats = {
    MANGA: 'Manga',
    NOVEL: 'Light Novel'
  };
  const createTorrentList = async (perfectMatch = true, title_type = 0, mal_id = null) => {
    // Cleanup exising elements
    try {
      document.querySelectorAll('.animebytes').forEach(e => e.remove());
    } catch (_unused) {
    }
    let type = unsafeWindow.location.pathname.match(/\/(anime|manga)\/[0-9]/);
    if (type === null) {
      return;
    }
    type = type[1];
    let vueMyBeloved;
    try {
      vueMyBeloved = document.getElementById('app').__vue__.$children.find(e => e.media);
    } catch (_unused2) {
      setTimeout(createTorrentList, 500);
      return;
    }
    const containerEl = document.querySelector('.content div.overview');
    if (containerEl) {
      var _vueMyBeloved$media$e, _vueMyBeloved$media$s, _vueMyBeloved$media$s2;
      const types = ['romaji', 'userPreferred', 'english', 'native'];
      let seriesName;
      try {
        seriesName = vueMyBeloved.media.title[types[title_type]].replaceAll(/[\]\[]/g, '');
      } catch (_unused3) {
        setTimeout(createTorrentList, 500);
        return;
      }
      const hentai = vueMyBeloved.media.isAdult;
      const epcount = (_vueMyBeloved$media$e = vueMyBeloved.media.episodes) != null ? _vueMyBeloved$media$e : 'manga';
      const seriesYear = (_vueMyBeloved$media$s = vueMyBeloved.media.seasonYear) != null ? _vueMyBeloved$media$s : (_vueMyBeloved$media$s2 = vueMyBeloved.media.startDate) == null ? void 0 : _vueMyBeloved$media$s2.year;
      let clonableEl = containerEl.querySelector('div .description-wrap');
      if (clonableEl === null) setTimeout(createTorrentList, 500);
      let endpoint = `https://animebytes.tv/scrape.php?torrent_pass=${passkey}&username=${username}&hentai=${Number(hentai)}&epcount=${epcount}&year=${seriesYear}&type=anime&searchstr=${encodeURIComponent(seriesName)}${type == 'manga' ? '&printedtype[' + formats[vueMyBeloved.media.format] + ']=1' : ''}`;
      if (!perfectMatch) endpoint = `https://animebytes.tv/scrape.php?torrent_pass=${passkey}&username=${username}&hentai=2&type=anime&searchstr=${encodeURIComponent(seriesName)}`;
      console.log(`[AnilistBytes] Using api endpoint: ${endpoint}`);
      let res = await GM_get(endpoint);
      if (!mal_id) {
        mal_id = await getMALId(vueMyBeloved.media.id, vueMyBeloved.media.type, vueMyBeloved.media.isAdult);
      }
      let ab_groups = (await res.json()).Groups;
      if (!ab_groups) {
        if (perfectMatch && title_type < 3) {
          console.log(`[AnilistBytes] Perfect match for ${types[title_type]} title failed, trying ${types[title_type + 1]} title`);
          return await createTorrentList(true, title_type + 1, mal_id);
        } else if (!perfectMatch && title_type < 3) {
          console.log(`[AnilistBytes] Imperfect match for ${types[title_type]} title failed, trying ${types[title_type + 1]} title`);
          return await createTorrentList(false, title_type + 1, mal_id);
        } else if (perfectMatch) {
          console.log(`[AnilistBytes] Perfect match for all titles failed, trying imperfect match.`);
          return await createTorrentList(false, 0);
        } else {
          console.log('[AnilistBytes] No match found giving up.');
          vueMyBeloved.$children.find(e => e.$options._componentTag == 'external-links')._props.links.push({
            color: '#ed106a',
            site: 'AnimeBytes [Search]',
            url: `https://animebytes.tv/torrents.php?searchstr=${encodeURIComponent(vueMyBeloved.media.title[types[1]].replaceAll(/[\]\[]/g, ''))}`,
            icon: 'https://anilistbytes.notmarek.com/AB.svg'
          });
          return;
        }
      }
      let data = null;
      for (let match of ab_groups) {
        if (!match.Links.MAL) {
          continue;
        }
        let mid = match.Links.MAL.match(/(\d+)/)[1];
        if (mid == mal_id) {
          data = match;
          break;
        }
      }
      console.log(data);
      if (!data && !perfectMatch) {
        data = ab_groups[0];
      } else if (!data) {
        return await createTorrentList(false, 0, mal_id);
      } else {
        perfectMatch = true;
      }
      vueMyBeloved.$children.find(e => e.$options._componentTag == 'external-links')._props.links.push({
        color: '#ed106a',
        site: 'AnimeBytes',
        url: `https://animebytes.tv/torrents.php?id=${data.ID}`,
        icon: 'https://anilistbytes.notmarek.com/AB.svg'
      });
      let entries = await Promise.all(data.Torrents.map(async torrent => {
        return await createTorrentEntry(torrent.Link, torrent.Property, torrent.Size, torrent.Leechers, torrent.Seeders, torrent.Snatched, torrent.RawDownMultiplier);
      }));
      let element = VM.h("div", {
        class: "animebytes"
      }, VM.h("h2", null, "AnilistBytes"), VM.h("p", {
        class: "description content-wrap"
      }, VM.h("h2", null, getDecodedString(data.FullName), "\xA0[", VM.h("a", {
        href: `https://animebytes.tv/torrents.php?id=${data.ID}`,
        style: "color: gray;",
        target: "_blank"
      }, "AB"), "]\xA0", perfectMatch ? null : VM.h("span", {
        style: "cursor: help; color: #ffaa00;",
        title: "Imperfect match means that the found anime may not be what you are looking for or that year/episode count/age rating simply don't match between anilist and AB."
      }, "(imperfect match)")), VM.h("h2", {
        class: "animebytes stats"
      }, VM.h("span", null, "Size"), VM.h("span", null, VM.h("img", {
        src: "https://anilistbytes.notmarek.com/snatched.svg",
        alt: "Snatches"
      })), VM.h("span", null, VM.h("img", {
        src: "https://anilistbytes.notmarek.com/seeders.svg",
        alt: "Seeders"
      })), VM.h("span", null, VM.h("img", {
        src: "https://anilistbytes.notmarek.com/leechers.svg",
        alt: "Leechers"
      }))), entries));
      containerEl.insertBefore(VM.m(element), clonableEl);
    } else {
      // check every 500ms if the page has loaded, so we can load our data
      setTimeout(() => createTorrentList(), 500);
    }
  };

  // hijack the window.history.pushState function to do shit for us on navigation
  (function (history) {
    var pushState = history.pushState;
    history.pushState = function (_state) {
      const res = pushState.apply(history, arguments);
      unsafeWindow.dispatchEvent(new Event('popstate'));
      return res;
    };
  })(unsafeWindow.history);
  unsafeWindow.addEventListener('popstate', () => {
    console.log(`[AnilistBytes] Soft navigated to ${unsafeWindow.location.pathname}`);
    setTimeout(createTorrentList, 500);
  });
  createTorrentList();
}

})();