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

Donghua

animexin

scraper animexin lengkap:p

Creator

qrtz

Language

javascript

Views

43

Copies

9

Base

https://animexin.dev/

Updated

09 Agu 2026

#donghua

Code

439
animexin.js
const axios = require('axios');
const cheerio = require('cheerio');
const https = require('https');

const BASE_URL = 'https://animexin.dev';

const USER_AGENTS = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; rv:121.0) Gecko/20100101 Firefox/121.0',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; SM-S921B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
];

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' : 'Linux';
  return {
    'User-Agent': ua,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9,id;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': ref || BASE_URL,
    'Cache-Control': 'no-cache',
    '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 = 300, max = 800) {
  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: 5,
        decompress: true,
        validateStatus: status => status >= 200 && status < 400
      });
      return res.data;
    } catch (e) {
      if (i < retries - 1) await randomDelay(1500, 4000);
      else throw e;
    }
  }
}

function normalizeUrl(input) {
  if (!input) return null;
  if (input.startsWith('http')) return input;
  if (input.startsWith('//')) return `https:${input}`;
  return `${BASE_URL}/${input.replace(/^\//, '')}`;
}

class AnimeXinScraper {
  constructor() {
    this.base = BASE_URL;
  }

  _clean(obj) {
    if (obj === null || obj === undefined) return undefined;
    if (Array.isArray(obj)) return obj.map(i => this._clean(i));
    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;
  }

  _parsePagination($) {
    const result = { current: 1, next: null, hasNext: false, total: null };
    const pageLinks = [];
    $('.pagination a, .pagination span, .page-numbers, .hpage a').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 = $('.pagination .page-numbers.current, .hpage .current').first();
    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 = normalizeUrl(nextLink.href);
      }
    }
    return result;
  }

  _parseEpisodeList($) {
    const episodes = [];
    $('.eplister ul li').each((i, el) => {
      const $el = $(el);
      const link = $el.find('a');
      const href = link.attr('href');
      const epNum = $el.find('.epl-num').text().trim();
      const title = $el.find('.epl-title').text().trim();
      const sub = $el.find('.epl-sub .status').text().trim();
      const date = $el.find('.epl-date').text().trim();
      if (href && epNum) {
        episodes.push({
          episode: epNum,
          title: title || epNum,
          sub: sub || 'Sub',
          date: date || null,
          url: normalizeUrl(href)
        });
      }
    });
    return episodes;
  }

  _parseServers($) {
    const servers = [];
    $('.mirror option').each((i, el) => {
      const value = $(el).attr('value');
      const label = $(el).text().trim();
      if (value && label && label !== 'Select Video Server') {
        let iframeSrc = null;
        try {
          const decoded = Buffer.from(value, 'base64').toString('utf-8');
          const iframeMatch = decoded.match(/src="([^"]+)"/);
          if (iframeMatch) iframeSrc = iframeMatch[1];
        } catch (e) {}
        servers.push({
          label,
          url: iframeSrc || null
        });
      }
    });
    return servers;
  }

  _parseDownloadLinks($) {
    const downloads = [];
    $('.soraddlx').each((i, el) => {
      const subtitle = $(el).find('.sorattlx h3').text().trim();
      const links = [];
      $(el).find('.soraurlx a').each((j, link) => {
        const label = $(link).text().trim();
        const href = $(link).attr('href');
        if (label && href && label !== 'Membership VIP' && !href.includes('ko-fi')) {
          links.push({ label, url: href });
        }
      });
      if (subtitle && links.length) {
        downloads.push({ subtitle, links });
      }
    });
    return downloads;
  }

  _buildResponse(page, url, data) {
    return this._clean({
      page,
      url,
      data
    });
  }

  async home(page = 1) {
    const url = page === 1 ? this.base : `${this.base}/page/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);

    const schedule = [];
    $('.listSchh').each((i, el) => {
      const day = $(el).find('h2').text().trim();
      const animeList = [];
      $(el).find('.subSchh a').each((j, link) => {
        const title = $(link).text().trim();
        const href = $(link).attr('href');
        if (title && href && href !== '#') {
          animeList.push({ title, url: normalizeUrl(href) });
        }
      });
      if (day && animeList.length) schedule.push({ day, animeList });
    });

    const latestReleases = [];
    $('.styleegg').each((i, el) => {
      const title = $(el).find('.eggtitle').text().trim() || $(el).find('.tt h2').text().trim();
      const episode = $(el).find('.eggepisode').text().trim();
      const type = $(el).find('.eggtype').text().trim();
      const link = $(el).find('a');
      const href = link.attr('href');
      const img = $(el).find('img').attr('src');
      if (title && href) {
        latestReleases.push({
          title,
          episode: episode || 'Ongoing',
          type: type || 'ONA',
          url: normalizeUrl(href),
          image: img || null
        });
      }
    });

    const popular = { weekly: [], monthly: [], allTime: [] };
    const popTypes = [
      { sel: '.wpop-weekly .leftseries', key: 'weekly' },
      { sel: '.wpop-monthly .leftseries', key: 'monthly' },
      { sel: '.wpop-alltime .leftseries', key: 'allTime' }
    ];
    popTypes.forEach(({ sel, key }) => {
      $(sel).each((i, el) => {
        const title = $(el).find('h4 a').text().trim();
        const href = $(el).find('h4 a').attr('href');
        const genres = $(el).find('span a').map((_, g) => $(g).text().trim()).get();
        const rating = $(el).find('.numscore').text().trim();
        if (title && href) {
          popular[key].push({
            title,
            url: normalizeUrl(href),
            genres,
            rating: rating || null
          });
        }
      });
    });

    return this._buildResponse('home', url, { schedule, latestReleases, popular });
  }

  async list(order = 'update', page = 1) {
    const url = page === 1
      ? `${this.base}/anime/?order=${order}`
      : `${this.base}/anime/page/${page}/?order=${order}`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);

    const animeList = [];
    $('.bsx').each((i, el) => {
      const link = $(el).find('a');
      const title = $(el).find('.tt h2').text().trim() || $(el).find('.tt').text().trim();
      const href = link.attr('href');
      const type = $(el).find('.typez').text().trim();
      const status = $(el).find('.bt .epx').text().trim() || $(el).find('.status').text().trim();
      const sub = $(el).find('.bt .sb').text().trim();
      const img = $(el).find('img').attr('src');
      const rating = $(el).find('.numscore').text().trim();
      const genres = $(el).find('.tt span a').map((_, g) => $(g).text().trim()).get();
      if (title && href) {
        animeList.push({
          title,
          url: normalizeUrl(href),
          type: type || 'ONA',
          status: status || 'Ongoing',
          sub: sub || 'Sub',
          image: img || null,
          rating: rating || null,
          genres
        });
      }
    });

    const pagination = this._parsePagination($);
    return this._buildResponse('list', url, { order, pagination, animeList });
  }

  async detail(input) {
    const url = normalizeUrl(input);
    if (!url) throw new Error('Invalid URL or slug');
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);

    const title = $('.infox h1').text().trim() || $('.entry-title').text().trim();
    const altTitle = $('.alter').text().trim() || null;
    const poster = $('.thumb img').attr('src') || $('.bigcover .ime img').attr('src') || null;
    const status = $('.spe span:contains("Status:")').text().replace('Status:', '').trim() || 'Ongoing';
    const network = $('.spe a[rel="tag"]').first().text().trim() || null;
    const released = $('.spe span:contains("Released:")').text().replace('Released:', '').trim() || null;
    const duration = $('.spe span:contains("Duration:")').text().replace('Duration:', '').trim() || null;
    const season = $('.spe a[rel="tag"]').eq(1).text().trim() || null;
    const country = $('.spe a[rel="tag"]').eq(2).text().trim() || null;
    const type = $('.spe span:contains("Type:")').text().replace('Type:', '').trim() || 'ONA';
    const episodes = $('.spe span:contains("Episodes:")').text().replace('Episodes:', '').trim() || null;
    const fansub = $('.spe span:contains("Fansub:")').text().replace('Fansub:', '').trim() || null;
    const postedBy = $('.spe .fn').text().trim() || null;
    const releasedOn = $('.spe span:contains("Released on:") .updated').text().trim() || null;
    const updatedOn = $('.spe span:contains("Updated on:") .updated').text().trim() || null;

    const genres = $('.genxed a').map((_, el) => $(el).text().trim()).get();
    const tags = $('.tags a').map((_, el) => $(el).text().trim()).get();
    const synopsis = $('.entry-content p').first().text().trim() || $('.synp .entry-content p').text().trim() || '';

    const episodesList = this._parseEpisodeList($);

    const rating = $('.wpd-rating-value .wpdrv').text().trim() || null;
    const ratingCount = $('.wpd-rating-value .wpdrc').text().trim() || null;

    let prevEpisode = null;
    let nextEpisode = null;
    const prevLink = $('.naveps .nvs:first-child a');
    const nextLink = $('.naveps .nvs:last-child a');
    if (prevLink.length && prevLink.attr('href')) {
      prevEpisode = normalizeUrl(prevLink.attr('href'));
    }
    if (nextLink.length && nextLink.attr('href') && !nextLink.text().includes('Next')) {
      nextEpisode = normalizeUrl(nextLink.attr('href'));
    }

    const recommended = [];
    $('.listupd .bs').slice(0, 10).each((i, el) => {
      const link = $(el).find('a');
      const titleRec = $(el).find('.tt h2').text().trim() || $(el).find('.tt').text().trim();
      const href = link.attr('href');
      const posterRec = $(el).find('img').attr('src');
      if (titleRec && href) {
        recommended.push({
          title: titleRec,
          url: normalizeUrl(href),
          poster: posterRec || null
        });
      }
    });

    return this._buildResponse('detail', url, {
      title,
      altTitle,
      poster,
      status,
      network,
      released,
      duration,
      season,
      country,
      type,
      episodes,
      fansub,
      postedBy,
      releasedOn,
      updatedOn,
      genres,
      tags,
      synopsis: synopsis || null,
      rating,
      ratingCount,
      episodesList,
      prevEpisode,
      nextEpisode,
      recommended
    });
  }

  async episode(input) {
    const url = normalizeUrl(input);
    if (!url) throw new Error('Invalid URL or slug');
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);

    const title = $('.entry-title').text().trim() || $('.title-section h1').text().trim();
    const seriesName = $('.single-info .infolimit h2').text().trim() || $('.single-info .spe span:contains("Series:") a').text().trim();
    const seriesUrl = $('.single-info .infolimit h2 a').attr('href') || null;
    const poster = $('.thumb img').attr('src') || $('.meta .tb img').attr('src') || null;
    const type = $('.epx').text().trim() || 'ONA';
    const sub = $('.lg').text().trim() || 'Sub';
    const releasedOn = $('.updated').text().trim() || null;
    const postedBy = $('.vcard .fn').text().trim() || null;

    const defaultPlayer = $('.player-embed iframe').attr('src') || null;
    let defaultVideoId = null;
    if (defaultPlayer) {
      try {
        const params = new URLSearchParams(new URL(defaultPlayer).search);
        defaultVideoId = params.get('video');
      } catch (e) {}
    }

    const servers = this._parseServers($);
    const downloads = this._parseDownloadLinks($);

    let prevEpisode = null;
    let nextEpisode = null;
    const prevLink = $('.naveps .nvs:first-child a');
    const nextLink = $('.naveps .nvs:last-child a');
    if (prevLink.length && prevLink.attr('href')) {
      prevEpisode = normalizeUrl(prevLink.attr('href'));
    }
    if (nextLink.length && nextLink.attr('href') && !nextLink.text().includes('Next')) {
      nextEpisode = normalizeUrl(nextLink.attr('href'));
    }

    const allEpisodesUrl = $('.naveps .nvsc a').attr('href') || null;
    const description = $('.entry-content .infx p').text().trim() || null;

    const otherEpisodes = [];
    $('.listupd .bsx').slice(0, 10).each((i, el) => {
      const link = $(el).find('a');
      const titleEp = $(el).find('.tt h2').text().trim() || $(el).find('.tt').text().trim();
      const href = link.attr('href');
      if (titleEp && href && href !== url) {
        otherEpisodes.push({
          title: titleEp,
          url: normalizeUrl(href)
        });
      }
    });

    return this._buildResponse('episode', url, {
      title,
      seriesName: seriesName || null,
      seriesUrl: normalizeUrl(seriesUrl),
      poster,
      type,
      sub,
      releasedOn,
      postedBy,
      defaultPlayer,
      defaultVideoId,
      servers,
      downloads,
      prevEpisode,
      nextEpisode,
      allEpisodesUrl: normalizeUrl(allEpisodesUrl),
      description,
      otherEpisodes
    });
  }

  async genres() {
    const html = await fetchHTML(`${this.base}/genres/`);
    const $ = cheerio.load(html);

    const genres = [];
    $('.taxindex li a').each((i, el) => {
      const name = $(el).find('.name').text().trim();
      const count = $(el).find('.count').text().trim();
      const href = $(el).attr('href');
      if (name && href) {
        genres.push({
          name,
          count: parseInt(count) || 0,
          url: normalizeUrl(href)
        });
      }
    });

    return this._buildResponse('genres', `${this.base}/genres/`, { total: genres.length, genres });
  }

  async genre(input, page = 1) {
    const slug = input.replace(`${this.base}/genres/`, '').replace(/\/$/, '').replace(/\/page\/\d+/, '');
    const url = page === 1
      ? `${this.base}/genres/${slug}/`
      : `${this.base}/genres/${slug}/page/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);

    const animeList = [];
    $('.bsx').each((i, el) => {
      const link = $(el).find('a');
      const title = $(el).find('.tt h2').text().trim() || $(el).find('.tt').text().trim();
      const href = link.attr('href');
      const type = $(el).find('.typez').text().trim();
      const status = $(el).find('.bt .epx').text().trim() || $(el).find('.status').text().trim();
      const sub = $(el).find('.bt .sb').text().trim();
      const img = $(el).find('img').attr('src');
      if (title && href) {
        animeList.push({
          title,
          url: normalizeUrl(href),
          type: type || 'ONA',
          status: status || 'Ongoing',
          sub: sub || 'Sub',
          image: img || null
        });
      }
    });

    const pagination = this._parsePagination($);
    return this._buildResponse('genre', url, { slug, pagination, animeList });
  }

  async release() {
    const html = await fetchHTML(`${this.base}/release-date/`);
    const $ = cheerio.load(html);

    const schedule = {};
    $('.schedulepage').each((i, el) => {
      const day = $(el).find('.releases h3 span').text().trim();
      const animeList = [];
      $(el).find('.bsx').each((j, item) => {
        const link = $(item).find('a');
        const title = $(item).find('.tt').text().trim();
        const href = link.attr('href');
        const time = $(item).find('.cndwn').text().trim() || $(item).find('.epx').text().trim();
        const episode = $(item).find('.epx').text().trim();
        const sub = $(item).find('.sb').text().trim();
        const img = $(item).find('img').attr('src');
        const rlsdt = $(item).find('.cndwn').attr('data-rlsdt') || null;
        const cndwn = $(item).find('.cndwn').attr('data-cndwn') || null;
        if (title && href) {
          animeList.push({
            title,
            url: normalizeUrl(href),
            time: time || 'Unknown',
            episode: episode || null,
            sub: sub || 'Sub',
            image: img || null,
            releaseTimestamp: rlsdt ? parseInt(rlsdt) : null,
            countdown: cndwn ? parseInt(cndwn) : null
          });
        }
      });
      if (day && animeList.length) schedule[day] = animeList;
    });

    return this._buildResponse('release', `${this.base}/release-date/`, { schedule });
  }

  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 results = [];
    $('.bsx').each((i, el) => {
      const link = $(el).find('a');
      const title = $(el).find('.tt h2').text().trim() || $(el).find('.tt').text().trim();
      const href = link.attr('href');
      const type = $(el).find('.typez').text().trim();
      const status = $(el).find('.bt .epx').text().trim() || $(el).find('.status').text().trim();
      const img = $(el).find('img').attr('src');
      if (title && href) {
        results.push({
          title,
          url: normalizeUrl(href),
          type: type || 'ONA',
          status: status || 'Ongoing',
          image: img || null
        });
      }
    });

    const pagination = this._parsePagination($);
    return this._buildResponse('search', url, { query, pagination, results });
  }
}

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

  (async () => {
    let result;
    try {
      switch (cmd) {
        case 'home':
          result = await scraper.home(parseInt(params[0]) || 1);
          break;
        case 'list':
          result = await scraper.list(params[0] || 'update', parseInt(params[1]) || 1);
          break;
        case 'detail':
          if (!params[0]) throw new Error('Slug or URL required');
          result = await scraper.detail(params[0]);
          break;
        case 'episode':
          if (!params[0]) throw new Error('Slug or URL required');
          result = await scraper.episode(params[0]);
          break;
        case 'genres':
          result = await scraper.genres();
          break;
        case 'genre':
          if (!params[0]) throw new Error('Genre slug or URL required');
          result = await scraper.genre(params[0], parseInt(params[1]) || 1);
          break;
        case 'release':
          result = await scraper.release();
          break;
        case 'search':
          if (!params[0]) throw new Error('Query required');
          result = await scraper.search(params[0], parseInt(params[1]) || 1);
          break;
        default:
          console.log(`
Commands:
  home [page]          - Scrape homepage
  list [order] [page]  - Scrape anime list (orders: update, latest, popular, rating)
  detail <slug/url>    - Scrape anime detail
  episode <slug/url>   - Scrape episode page
  genres               - Scrape all genres
  genre <slug/url>     - Scrape specific genre
  release              - Scrape release schedule
  search <query>       - Search anime

Examples:
  node animexin.js home
  node animexin.js list popular 2
  node animexin.js detail swallowed-star-season-5
  node animexin.js detail https://animexin.dev/swallowed-star-season-5
  node animexin.js episode swallowed-star-season-5-episode-24-232-indonesia-english-sub
  node animexin.js genres
  node animexin.js genre action
  node animexin.js genre https://animexin.dev/genres/action
  node animexin.js release
  node animexin.js search swallowed
          `);
          process.exit(0);
      }
      console.log(JSON.stringify(result, null, 2));
    } catch (err) {
      console.error(JSON.stringify({ error: err.message }));
      process.exit(1);
    }
  })();
}

module.exports = AnimeXinScraper;

Rating

5.0(1)

Gimana snippet ini menurutmu?

</> Embed

Embed ke website / blog

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

Related Snippets

dongub

scraper donghub.vip lengkap:p

60
24

anichin

Anichi lengkap dengan link stream.

33
23