qrtzcode
HomeDocsAPILeaderboardChangelog
Contribute
Docs

qrtzcode

Kumpulan gist & snippet pribadi — anime, AI tools, scraper, dan randomness.

Navigate

HomeDocsAPI Reference

Links

© 2026 qrtz. All snippets welcome.

ESC

Navigate

Links

Anime

animeindo

scraper animeindo lengkap.

Creator

qrtz

Language

javascript

Views

25

Copies

5

Base

https://animeindo.me

Updated

23 Jun 2026

#anime

Code

255
animeindo.js
const axios = require('axios');
const cheerio = require('cheerio');
const https = require('https');

const BASE_URL = 'https://animeindo.me';

const USER_AGENTS = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; rv:133.0) Gecko/20100101 Firefox/133.0',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; SM-S921B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.104 Mobile Safari/537.36',
  'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
];

let uaIndex = 0;

function getHeaders(ref) {
  const ua = USER_AGENTS[uaIndex % USER_AGENTS.length];
  uaIndex++;
  const isMobile = ua.includes('Mobile') || ua.includes('iPhone') || ua.includes('Android');
  const platform = ua.includes('Windows') ? 'Windows' : ua.includes('Mac') ? 'macOS' : ua.includes('Linux') ? 'Linux' : 'Unknown';
  return {
    'User-Agent': ua,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
    'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': ref || 'https://animeindo.me/',
    'Cache-Control': 'no-cache, no-store, must-revalidate',
    'Pragma': 'no-cache',
    'DNT': '1',
    'Sec-Ch-Ua': `"${ua.includes('Chrome') ? 'Google Chrome' : 'Chromium'}"`,
    'Sec-Ch-Ua-Mobile': isMobile ? '?1' : '?0',
    'Sec-Ch-Ua-Platform': `"${platform}"`,
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'Connection': 'keep-alive'
  };
}

function randomDelay(min = 500, max = 2000) {
  return new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (max - min + 1)) + min));
}

async function fetchHTML(url, retries = 5, ref = null) {
  for (let i = 0; i < retries; i++) {
    try {
      await randomDelay(300, 800);
      const res = await axios({
        url,
        method: 'GET',
        headers: getHeaders(ref || url),
        timeout: 30000,
        httpsAgent: new https.Agent({ rejectUnauthorized: false, keepAlive: true }),
        maxRedirects: 0,
        decompress: true,
        validateStatus: status => status >= 200 && status < 400
      });
      return res.data;
    } catch (e) {
      if (e.response && e.response.status >= 300 && e.response.status < 400) return e.response.data;
      if (i < retries - 1) await randomDelay(1500, 4000);
      else throw e;
    }
  }
}

async function resolveRedirect(url) {
  try {
    const res = await axios.get(url, {
      maxRedirects: 10,
      validateStatus: status => status >= 200 && status < 400,
      headers: {
        'User-Agent': USER_AGENTS[0],
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Referer': 'https://animeindo.me/'
      },
      httpsAgent: new https.Agent({ rejectUnauthorized: false }),
      timeout: 15000
    });
    return res.request.res.responseUrl || res.config.url;
  } catch (e) {
    return url;
  }
}

function decodeBase64(str) {
  try {
    return Buffer.from(str, 'base64').toString('utf-8');
  } catch (e) {
    return null;
  }
}

function extractSrcFromIframe(html) {
  const match = html.match(/<iframe[^>]*src=["']([^"']*)["']/i);
  return match ? match[1] : null;
}

class AnimeIndoScraper {
  constructor() {
    this.base = BASE_URL;
    this.creator = 'rynaqrtz';
  }

  _clean(obj) {
    if (obj === null || obj === undefined) return undefined;
    if (Array.isArray(obj)) {
      const filtered = obj.map(i => this._clean(i)).filter(i => i !== undefined);
      return filtered.length ? filtered : undefined;
    }
    if (typeof obj === 'object') {
      const result = {};
      for (const key of Object.keys(obj)) {
        const val = this._clean(obj[key]);
        if (val !== undefined) result[key] = val;
      }
      return Object.keys(result).length ? result : undefined;
    }
    return obj;
  }

  _parseAnimeCard($, element) {
    const $el = $(element);
    const link = $el.find('a').attr('href');
    const title = $el.find('.tt h2').text().trim() || $el.find('.tt').text().trim();
    const poster = $el.find('img').attr('data-src') || $el.find('img').attr('src') || null;
    const type = $el.find('.typez').text().trim() || null;
    const status = $el.find('.status').text().trim() || null;
    const episode = $el.find('.epx').text().trim() || null;
    const sub = $el.find('.sb').text().trim() || null;
    if (!link || !title) return null;
    return {
      title,
      url: link.startsWith('http') ? link : this.base + link,
      poster,
      type,
      status,
      episode,
      sub
    };
  }

  _parsePagination($) {
    const result = { current: 1, next: null, hasNext: false, total: null };
    const pageLinks = [];
    $('.hpage a, .pagination a, .pagination span, .page-numbers').each((i, el) => {
      const href = $(el).attr('href');
      const text = $(el).text().trim();
      if (href) pageLinks.push({ text, href });
    });
    const numbers = pageLinks.filter(l => /^\d+$/.test(l.text)).map(l => parseInt(l.text));
    if (numbers.length) result.total = Math.max(...numbers);
    const current = $('.page-numbers.current, .pagination .current, .hpage .current');
    if (current.length) {
      const t = current.text().trim();
      if (/^\d+$/.test(t)) result.current = parseInt(t);
    }
    if (result.total && result.current < result.total) {
      result.hasNext = true;
      const nextLink = pageLinks.find(l => l.text === 'Next' || l.text === '»' || l.text.toLowerCase().includes('next'));
      if (nextLink && nextLink.href) {
        result.next = nextLink.href.startsWith('http') ? nextLink.href : this.base + nextLink.href;
      }
    }
    return result;
  }

  _parseEpisodeList($) {
    const episodes = [];
    $('.epcheck .eplister ul li a, #singlepisode .episodelist ul li a, .eplist .episode-item a').each((i, el) => {
      const $el = $(el);
      const url = $el.attr('href');
      const num = $el.find('.epl-num').text().trim() || $el.find('.ep-num').text().trim() || (url ? parseInt(url.match(/episode-(\d+)/)?.[1]) : null);
      const title = $el.find('.epl-title').text().trim() || $el.find('.ep-title').text().trim() || $el.find('h3').text().trim() || null;
      const date = $el.find('.epl-date').text().trim() || $el.find('.ep-date').text().trim() || null;
      if (url && num) {
        episodes.push({
          episode: parseInt(num) || num,
          title: title || `Episode ${num}`,
          url: url.startsWith('http') ? url : this.base + url,
          releaseDate: date || null
        });
      }
    });
    return episodes.sort((a, b) => parseInt(a.episode) - parseInt(b.episode));
  }

  async resolveStreamUrls(episodeUrl) {
    const html = await fetchHTML(episodeUrl);
    const $ = cheerio.load(html);
    const streams = [];

    const defaultIframe = $('#pembed iframe, .player-embed iframe, #iframedc');
    let defaultPlayer = null;
    if (defaultIframe.length) {
      let src = defaultIframe.attr('src') || defaultIframe.attr('data-src') || defaultIframe.attr('data-litespeed-src');
      if (src && src !== 'about:blank') {
        defaultPlayer = src;
      }
    }

    $('select.mirror option').each((i, el) => {
      const $opt = $(el);
      const label = $opt.text().trim();
      const value = $opt.val();
      if (!value || value === '') return;
      const decodedHtml = decodeBase64(value);
      if (!decodedHtml) return;
      const src = extractSrcFromIframe(decodedHtml);
      if (src) {
        streams.push({ quality: label, url: src });
      }
    });

    for (const stream of streams) {
      let finalUrl = stream.url;
      if (stream.url.includes('link.desustream.com') || stream.url.includes('short.icu') || stream.url.includes('tinyurl')) {
        try { finalUrl = await resolveRedirect(stream.url); } catch (e) {}
      }
      stream.url = finalUrl;
    }

    return { defaultPlayer, streams };
  }

  async home(page = 1) {
    const url = page === 1 ? this.base + '/' : this.base + `/page/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.listupd .bs, .listupd .excstf .bs, .popconslide .bs').each((i, el) => {
      const card = this._parseAnimeCard($, el);
      if (card) items.push(card);
    });
    const pagination = this._parsePagination($);
    return this._clean({ creator: this.creator, page: 'home', data: { url, pagination, items } });
  }

  async ongoing(page = 1) {
    const url = page === 1 ? this.base + '/anime-ongoing/' : this.base + `/anime-ongoing/page/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.listupd .bs, .listupd.cp .bs').each((i, el) => {
      const card = this._parseAnimeCard($, el);
      if (card) items.push(card);
    });
    const pagination = this._parsePagination($);
    return this._clean({ creator: this.creator, page: 'ongoing', data: { url, pagination, items } });
  }

  async schedule() {
    const url = this.base + '/jadwal-anime/';
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const schedule = {};
    $('.schedulepage').each((i, el) => {
      const $el = $(el);
      const dayClass = $el.attr('class').match(/sch_(\w+)/);
      const day = dayClass ? dayClass[1].charAt(0).toUpperCase() + dayClass[1].slice(1) : null;
      const items = [];
      $el.find('.bs').each((j, card) => {
        const $card = $(card);
        const title = $card.find('.tt').text().trim();
        const url = $card.find('a').attr('href');
        const poster = $card.find('img').attr('data-src') || $card.find('img').attr('src') || null;
        const episode = $card.find('.epx').text().trim() || null;
        const sub = $card.find('.sb').text().trim() || null;
        const releaseTime = $card.find('.epx.cndwn').attr('data-rlsdt') || null;
        if (title && url) {
          items.push({
            title,
            url: url.startsWith('http') ? url : this.base + url,
            poster,
            episode,
            sub,
            releaseTime
          });
        }
      });
      if (day && items.length) schedule[day] = items;
    });
    return this._clean({ creator: this.creator, page: 'schedule', data: { url, schedule } });
  }

  async list(page = 1, filters = {}) {
    let url = this.base + '/anime/';
    const params = new URLSearchParams();
    if (page > 1) params.set('page', page);
    if (filters.status) params.set('status', filters.status);
    if (filters.type) params.set('type', filters.type);
    if (filters.order) params.set('order', filters.order);
    if (filters.sub) params.set('sub', filters.sub);
    if (filters.genre && filters.genre.length) params.set('genre[]', filters.genre.join(','));
    if (filters.season && filters.season.length) params.set('season[]', filters.season.join(','));
    const query = params.toString();
    if (query) url += '?' + query;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.listupd .bs').each((i, el) => {
      const card = this._parseAnimeCard($, el);
      if (card) items.push(card);
    });
    const pagination = this._parsePagination($);
    return this._clean({ creator: this.creator, page: 'list', data: { url, filters, pagination, items } });
  }
  
  async popular(page = 1) { return this.list(page, { order: 'popular' }); }
  async rating(page = 1)  { return this.list(page, { order: 'rating' }); }
  async update(page = 1)  { return this.list(page, { order: 'update' }); }
  async completed(page = 1) { return this.list(page, { status: 'completed', order: 'popular' }); }
  async movie(page = 1)   { return this.list(page, { type: 'movie', order: 'rating' }); }
  async tv(page = 1)      { return this.list(page, { type: 'tv', order: 'popular' }); }

  async search(query, page = 1) {
    const url = page === 1 ? this.base + `/?s=${encodeURIComponent(query)}` : this.base + `/page/${page}/?s=${encodeURIComponent(query)}`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.listupd .bs').each((i, el) => {
      const card = this._parseAnimeCard($, el);
      if (card) items.push(card);
    });
    const pagination = this._parsePagination($);
    return this._clean({ creator: this.creator, page: 'search', data: { url, query, pagination, items } });
  }

  async detail(slug) {
    const url = this.base + `/anime/${slug}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const title = $('.infox .infolimit h2, .infox h1').first().text().trim();
    const poster = $('.infox .thumb img, .bigcontent .thumb img').attr('data-src') || $('.infox .thumb img, .bigcontent .thumb img').attr('src') || null;
    const rating = $('.numscore, .rating .numscore').text().trim() || null;
    const ratingPercent = $('.rtb span').attr('style') ? parseFloat($('.rtb span').attr('style').match(/width:([\d.]+)%/)?.[1]) : null;
    const alternative = $('.alter').text().trim() || null;
    const synopsis = $('.desc, .entry-content p').first().text().trim() || null;
    const info = {};
    $('.spe span, .info-content .spe span').each((i, el) => {
      const text = $(el).text().trim();
      const parts = text.split(':');
      if (parts.length >= 2) {
        const key = parts[0].trim().replace('<b>', '').replace('</b>', '');
        const val = parts.slice(1).join(':').trim();
        if (key && val) info[key] = val;
      }
    });
    const genres = [];
    $('.genxed a, .info-content .genxed a').each((i, el) => {
      genres.push($(el).text().trim());
    });
    const episodes = this._parseEpisodeList($);
    const recommended = [];
    $('.listupd .bs, .recommended .bs').each((i, el) => {
      const card = this._parseAnimeCard($, el);
      if (card) recommended.push(card);
    });
    return this._clean({
      creator: this.creator,
      page: 'detail',
      slug,
      data: {
        url,
        title,
        poster,
        rating,
        ratingPercent,
        alternative,
        synopsis,
        info,
        genres,
        episodes,
        recommended: recommended.slice(0, 10)
      }
    });
  }

  async episode(slugOrUrl) {
    let url, slug, episodeNum;
    if (slugOrUrl.includes('http')) {
      url = slugOrUrl;
      const match = url.match(/\/anime\/([^\/]+)\/|\/([^\/]+)-episode-(\d+)/);
      if (match) {
        slug = match[1] || match[2];
        episodeNum = parseInt(match[3]) || null;
      }
    } else {
      const match = slugOrUrl.match(/^(.+?)-episode-(\d+)$/);
      if (match) {
        slug = match[1];
        episodeNum = parseInt(match[2]);
        url = this.base + `/${slug}-episode-${episodeNum}-sub-indo/`;
      } else {
        throw new Error('Invalid format. Use slug-episode-number');
      }
    }

    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const title = $('.entry-title, .title-section h1').first().text().trim();
    const poster = $('.tb img, .thumb img').attr('data-src') || $('.tb img, .thumb img').attr('src') || null;
    const info = {};
    $('.spe span, .single-info .infox .spe span').each((i, el) => {
      const text = $(el).text().trim();
      const parts = text.split(':');
      if (parts.length >= 2) {
        const key = parts[0].trim().replace('<b>', '').replace('</b>', '');
        const val = parts.slice(1).join(':').trim();
        if (key && val) info[key] = val;
      }
    });
    const genres = [];
    $('.genxed a').each((i, el) => {
      genres.push($(el).text().trim());
    });
    const synopsis = $('.desc, .entry-content p').first().text().trim() || null;
    const episodes = this._parseEpisodeList($);
    const prev = $('.naveps .nvs:first-child a, #prev a').attr('href') || null;
    const next = $('.naveps .nvs:last-child a, #next a').attr('href') || null;
    const allEpisodesLink = $('.naveps .nvsc a').attr('href') || null;

    const streamResult = await this.resolveStreamUrls(url);

    return this._clean({
      creator: this.creator,
      page: 'episode',
      slug,
      episode: episodeNum,
      data: {
        url,
        title,
        poster,
        defaultPlayer: streamResult.defaultPlayer,
        streams: streamResult.streams,
        info,
        genres,
        synopsis,
        episodes,
        prevEpisode: prev ? (prev.startsWith('http') ? prev : this.base + prev) : null,
        nextEpisode: next ? (next.startsWith('http') ? next : this.base + next) : null,
        allEpisodesLink: allEpisodesLink ? (allEpisodesLink.startsWith('http') ? allEpisodesLink : this.base + allEpisodesLink) : null
      }
    });
  }
}

if (require.main === module) {
  const args = process.argv.slice(2);
  const cmd = args[0];
  const params = args.slice(1);
  const scraper = new AnimeIndoScraper();

  (async () => {
    let result;
    try {
      switch (cmd) {
        case 'home':
          result = await scraper.home(parseInt(params[0]) || 1);
          break;
        case 'ongoing':
          result = await scraper.ongoing(parseInt(params[0]) || 1);
          break;
        case 'schedule':
          result = await scraper.schedule();
          break;
        case 'popular':
          result = await scraper.popular(parseInt(params[0]) || 1);
          break;
        case 'rating':
          result = await scraper.rating(parseInt(params[0]) || 1);
          break;
        case 'update':
          result = await scraper.update(parseInt(params[0]) || 1);
          break;
        case 'completed':
          result = await scraper.completed(parseInt(params[0]) || 1);
          break;
        case 'movie':
          result = await scraper.movie(parseInt(params[0]) || 1);
          break;
        case 'tv':
          result = await scraper.tv(parseInt(params[0]) || 1);
          break;
        case 'search':
          if (!params[0]) throw new Error('Query required');
          result = await scraper.search(params[0], parseInt(params[1]) || 1);
          break;
        case 'detail':
          if (!params[0]) throw new Error('Slug required');
          result = await scraper.detail(params[0]);
          break;
        case 'episode':
          if (!params[0]) throw new Error('Slug-episode-number or URL required');
          result = await scraper.episode(params[0]);
          break;
        case 'stream':
          if (!params[0]) throw new Error('Episode URL required');
          const streams = await scraper.resolveStreamUrls(params[0]);
          console.log(JSON.stringify(streams, null, 2));
          return;
        default:
          console.error(`Commands:
  home [page]          - Latest episodes from homepage
  ongoing [page]       - List ongoing anime
  schedule             - Weekly release schedule
  popular [page]       - Most popular anime
  rating [page]        - Highest rated anime
  update [page]        - Recently updated anime
  completed [page]     - Completed anime
  movie [page]         - Anime movies
  tv [page]            - TV series
  search <query> [page]- Search anime
  detail <slug>        - Full anime details
  episode <slug-ep-num> - Episode with stream links
  stream <episode-url> - Extract video URLs episode only`);
          process.exit(1);
      }
      console.log(JSON.stringify(result, null, 2));
    } catch (err) {
      console.error(JSON.stringify({ error: err.message }));
      process.exit(1);
    }
  })();
}

module.exports = AnimeIndoScraper;

Rating

—(0)

Gimana snippet ini menurutmu?

</> Embed

Embed ke website / blog

<iframe src="https://qrtzcode.vercel.app/api/embed/anime/animeindo" style="width:100%;height:400px;border:none;border-radius:12px;"></iframe>

Related Snippets

otakudesu

seperti biasa bebas kalian modifikasi ubah credit ama creatornya, ak gabakal baper kok😹, semoga bermanfaat ya^^.

66
37

nimegami

Semoga bermanfaat^^.

35
11

Nonton Anime

yh.

10
6