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

Manga

mangadistrict

β—ˆ π˜Ώπ˜Όπ™π™π˜Όπ™ π˜Ύπ™Šπ™ˆπ™ˆπ˜Όπ™‰π˜Ώ

  1. home [page] β†’ Menampilkan manga/anime terbaru dari homepage β†’ Contoh: node mangadistrict.js home β†’ Contoh: node mangadistrict.js home 2

  2. search <query> [page] β†’ Mencari manga/anime berdasarkan judul β†’ Contoh: node mangadistrict.js search "one piece" β†’ Contoh: node mangadistrict.js search "naruto" 2

  3. series [orderby] [page] β†’ Menampilkan semua series dengan filter urutan β†’ orderby: latest, title, trending, rating, views, new, modified β†’ Default: latest β†’ Contoh: node mangadistrict.js series rating β†’ Contoh: node mangadistrict.js series title 2

  4. genre <slug> [orderby] [page] β†’ Filter manga berdasarkan genre β†’ slug: uncensored, action, romance, comedy, dll β†’ orderby: latest, title, trending, rating, views, new, modified β†’ Contoh: node mangadistrict.js genre uncensored β†’ Contoh: node mangadistrict.js genre action rating 2

  5. tag <slug> [orderby] [page] β†’ Filter manga berdasarkan tag β†’ slug: tsundere, school-life, yuri, dll β†’ orderby: latest, title, trending, rating, views, new, modified β†’ Contoh: node mangadistrict.js tag tsundere β†’ Contoh: node mangadistrict.js tag "school-life" title 2

  6. release <year> [orderby] [page] β†’ Filter manga berdasarkan tahun rilis β†’ year: 2026, 2025, 2024, dll β†’ orderby: latest, title, trending, rating, views, new, modified β†’ Contoh: node mangadistrict.js release 2026 β†’ Contoh: node mangadistrict.js release 2025 rating 2

  7. detail <slug|url> β†’ Menampilkan informasi lengkap series + daftar chapter β†’ slug: one-piece, my-brothers-slipped-inside-me-in-the-bathtub-overflow-uncensored β†’ Contoh: node mangadistrict.js detail "one-piece" β†’ Contoh: node mangadistrict.js detail "https://mangadistrict.com/series/one-piece/"

  8. chapter <url> β†’ Mengambil gambar chapter manga (auto-detect video) β†’ Jika URL adalah anime, otomatis ambil video stream β†’ Contoh: node mangadistrict.js chapter "https://mangadistrict.com/series/.../chapter-1/" β†’ Contoh: node mangadistrict.js chapter "https://mangadistrict.com/series/animation-.../s1-e1/"

  9. episode-video <url> β†’ Memaksa ekstraksi video dari anime (tanpa auto-detect) β†’ Contoh: node mangadistrict.js episode-video "https://mangadistrict.com/series/animation-.../s1-e1/"

Creator

qrtz

Language

javascript

Views

13

Copies

4

Base

https://mangadistrict.com

Updated

24 Jun 2026

#18+#scraper#manga

Code

134
mangadistrict.js
const axios = require('axios');
const cheerio = require('cheerio');
const https = require('https');

const userAgents = [
  '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 (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 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.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'
];

const BASE_URL = 'https://mangadistrict.com';
let uaIndex = 0;

function getHeaders(referer) {
  const ua = userAgents[uaIndex % userAgents.length];
  uaIndex++;
  return {
    'User-Agent': ua,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': referer || BASE_URL + '/',
    'Sec-Ch-Ua': '"Not A(Brand";v="99", "Google Chrome";v="131", "Chromium";v="131"',
    'Sec-Ch-Ua-Mobile': '?0',
    'Sec-Ch-Ua-Platform': '"Windows"',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'Connection': 'keep-alive',
    'Cache-Control': 'max-age=0'
  };
}

async function fetchWithRetry(url, retries = 5, referer = null) {
  const headers = getHeaders(referer || url);
  const config = {
    url,
    method: 'GET',
    headers,
    timeout: 30000,
    httpsAgent: new https.Agent({ rejectUnauthorized: false }),
    maxRedirects: 5,
    decompress: true,
    validateStatus: status => status >= 200 && status < 400
  };
  let lastError;
  for (let i = 0; i < retries; i++) {
    try {
      const response = await axios(config);
      return response;
    } catch (err) {
      if (err.response && err.response.status >= 300 && err.response.status < 400) {
        return err.response;
      }
      lastError = err;
      if (err.response && err.response.status === 403) {
        await new Promise(r => setTimeout(r, 500 + Math.random() * 1000));
        continue;
      }
      if (i < retries - 1) await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
  throw lastError || new Error('Fetch failed after retries');
}

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

function parseChapterNumber(text) {
  if (!text) return null;
  const match = text.match(/(?:Vol\.?\s*\d+[:\s]*)?[Cc]h(?:apter)?\s*(\d+(?:\.\d+)?)/);
  return match ? parseFloat(match[1]) : null;
}

class MangaDistrictScraper {
  constructor() {
    this.creator = 'rynaqrtz';
    this.baseUrl = BASE_URL;
  }

  async list(url) {
    const html = (await fetchWithRetry(url)).data;
    const $ = cheerio.load(html);

    const items = [];
    $('.page-item-detail.manga').each((i, el) => {
      const $el = $(el);

      const titleEl = $el.find('.post-title h3 a, .post-title h1 a').first();
      const title = titleEl.text().trim();
      const link = titleEl.attr('href');

      if (!title || !link) return;

      const poster = $el.find('.item-thumb img, .summary_image img').first().attr('src') || null;

      let rating = null;
      const ratingText = $el.find('.score.font-meta.total_votes, .post-rating .score').text().trim();
      if (ratingText) {
        const match = ratingText.match(/([\d.]+)/);
        if (match) rating = parseFloat(match[1]);
      }

      let status = null;
      const badges = [];
      $el.find('.manga-title-badges .text').each((j, b) => {
        const txt = $(b).text().trim();
        badges.push(txt);
        if (['Ongoing', 'Completed', 'Hiatus', 'On-Going', 'Complete'].includes(txt)) {
          status = txt;
        }
      });

      const chapterEl = $el.find('.list-chapter .chapter-item:first-child .chapter a').first();
      const latestChapter = {
        title: chapterEl.text().trim() || null,
        url: chapterEl.attr('href') || null
      };

      let updateTime = null;
      const timeSelectors = [
        '.list-chapter .chapter-item:first-child .post-on .timediff',
        '.list-chapter .chapter-item:first-child .post-on time',
        '.chapter-date',
        '.post-on time'
      ];
      for (const sel of timeSelectors) {
        const el = $el.find(sel).first();
        if (el.length) {
          updateTime = el.text().trim() || el.attr('datetime') || null;
          if (updateTime) break;
        }
      }

      const viewsEl = $el.find('.list-chapter .chapter-item:first-child .views').first();
      const views = viewsEl.text().trim() || null;

      const genres = [];
      const genreSelectors = [
        '.mg_genres .summary-content a',
        '.genres-content a',
        '.post-content_item.mg_genres .summary-content a',
        '.item-summary .genres a'
      ];
      for (const sel of genreSelectors) {
        const found = $el.find(sel);
        if (found.length) {
          found.each((j, g) => genres.push($(g).text().trim()));
          break;
        }
      }

      const authorSelectors = ['.mg_author .summary-content', '.author-content a'];
      let author = null;
      for (const sel of authorSelectors) {
        const el = $el.find(sel).first();
        if (el.length) { author = el.text().trim() || null; break; }
      }

      const artistSelectors = ['.mg_artist .summary-content', '.artist-content a'];
      let artist = null;
      for (const sel of artistSelectors) {
        const el = $el.find(sel).first();
        if (el.length) { artist = el.text().trim() || null; break; }
      }

      const yearSelectors = ['.release-year', '.mg_release_year .summary-content'];
      let year = null;
      for (const sel of yearSelectors) {
        const el = $el.find(sel).first();
        if (el.length) { year = el.text().trim() || null; break; }
      }

      items.push({
        title,
        link: link.startsWith('http') ? link : this.baseUrl + link,
        poster,
        rating,
        status,
        badges,
        latestChapter,
        updateTime,
        views,
        genres,
        author,
        artist,
        year
      });
    });

    let next = null;
    const nextEl = $('.wp-pagenavi .page-numbers.next');
    if (nextEl.length) next = nextEl.attr('href');

    const currentPage = parseInt($('.wp-pagenavi .page-numbers.current').text()) || 1;

    return clean({
      creator: this.creator,
      page: 'list',
      data: {
        url,
        count: items.length,
        currentPage,
        items,
        next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null
      }
    });
  }

  async detail(slug) {
    const url = slug.startsWith('http') ? slug : this.baseUrl + '/series/' + slug.replace(/^\/+/, '');
    const html = (await fetchWithRetry(url)).data;
    const $ = cheerio.load(html);

    const title = $('.profile-manga .post-title h1').text().trim() || $('meta[property="og:title"]').attr('content') || '';
    const poster = $('.profile-manga .summary_image img').first().attr('src') || null;

    const altNames = [];
    const altEl = $('.post-content_item:contains("Alternative") .summary-content');
    if (altEl.length) {
      altEl.text().split(',').forEach(s => {
        const trimmed = s.trim();
        if (trimmed) altNames.push(trimmed);
      });
    }

    const author = $('.mg_author .summary-content').text().trim() || null;
    const artist = $('.mg_artist .summary-content').text().trim() || null;

    const genres = [];
    $('.mg_genres .summary-content a, .genres-content a').each((i, el) => {
      genres.push($(el).text().trim());
    });

    const year = $('.release-year, .mg_release_year .summary-content').text().trim() || null;
    const status = $('.mg_status .summary-content').text().trim() || null;

    let rating = null;
    let ratingCount = null;
    const ratingEl = $('.post-rating .score');
    if (ratingEl.length) {
      rating = parseFloat(ratingEl.text().trim());
      const countEl = $('.post-rating .count');
      if (countEl.length) {
        const countText = countEl.text().trim();
        const match = countText.match(/(\d+)/);
        if (match) ratingCount = parseInt(match[1]);
      }
    }

    const description = $('.description-summary').text().trim() || null;

    const hasLoadMore = $('.load-more-chapters, .loadmore-action').length > 0;
    let chapters = [];
    $('.page-content-listing .wp-manga-chapter, .listing-chapters_wrap .wp-manga-chapter, .version-chap .wp-manga-chapter').each((i, el) => {
      const $el = $(el);
      const linkEl = $el.find('a').first();
      const title = linkEl.find('.chap-title').text().trim() || linkEl.text().trim();
      const url = linkEl.attr('href');
      const date = $el.find('.chap-date').text().trim() || null;
      const number = parseChapterNumber(title);

      chapters.push({
        number,
        title,
        url: url ? (url.startsWith('http') ? url : this.baseUrl + url) : null,
        date
      });
    });

    return clean({
      creator: this.creator,
      page: 'detail',
      data: {
        url,
        slug,
        title,
        poster,
        altNames,
        author,
        artist,
        genres,
        year,
        status,
        rating,
        ratingCount,
        description,
        hasLoadMore,
        chapters
      }
    });
  }

  async episodeVideo(url) {
    const html = (await fetchWithRetry(url, 5, url)).data;
    const $ = cheerio.load(html);

    let iframeSrc = $('iframe').first().attr('src') || null;
    if (iframeSrc && iframeSrc.includes('discord.com')) iframeSrc = null;

    const videoSrc = $('video source').first().attr('src') || $('video').first().attr('src') || null;
    const videoLinks = [];
    $('a[href$=".mp4"], a[href$=".m3u8"]').each((i, el) => {
      videoLinks.push($(el).attr('href'));
    });

    let jsonData = null;
    $('script:not([src])').each((i, el) => {
      const text = $(el).html();
      if (text && (text.includes('video') || text.includes('player'))) {
        try {
          const match = text.match(/(\{.*"video".*\})/);
          if (match) jsonData = JSON.parse(match[1]);
        } catch(e) {}
      }
    });

    const images = [];
    $('.reading-content img, .entry-content img').each((i, el) => {
      const src = $(el).attr('src') || $(el).attr('data-src');
      if (src && !src.includes('data:image')) {
        images.push({ src, index: i + 1 });
      }
    });

    let prevEpisode = null;
    const prevEl = $('.nav-previous a, .nav-links a:contains("Previous")');
    if (prevEl.length) prevEpisode = prevEl.attr('href');

    let nextEpisode = null;
    const nextEl = $('.nav-next a, .nav-links a:contains("Next")');
    if (nextEl.length) nextEpisode = nextEl.attr('href');

    const episodes = [];
    $('select.paged-links option').each((i, el) => {
      const val = $(el).attr('value');
      const text = $(el).text().trim();
      if (val) {
        episodes.push({
          title: text,
          url: val.startsWith('http') ? val : this.baseUrl + val
        });
      }
    });

    return clean({
      creator: this.creator,
      page: 'episode_video',
      data: {
        url,
        title: $('meta[property="og:title"]').attr('content') || null,
        iframeSrc,
        videoSrc,
        videoLinks,
        jsonData,
        images,
        prevEpisode: prevEpisode ? (prevEpisode.startsWith('http') ? prevEpisode : this.baseUrl + prevEpisode) : null,
        nextEpisode: nextEpisode ? (nextEpisode.startsWith('http') ? nextEpisode : this.baseUrl + nextEpisode) : null,
        episodes
      }
    });
  }

  async chapter(url) {
    const html = (await fetchWithRetry(url, 5, url)).data;
    const $ = cheerio.load(html);

    const hasVideo = $('video').length > 0 ||
                     $('source[src$=".m3u8"]').length > 0 ||
                     $('a[href$=".m3u8"], a[href$=".mp4"]').length > 0 ||
                     ($('script:not([src])').text().includes('video') && $('script:not([src])').text().includes('m3u8'));

    if (hasVideo) {
      return this.episodeVideo(url);
    }

    const title = $('.entry-header .breadcrumb span:last-child').text().trim() ||
                  $('.entry-header .breadcrumb a:last-child').text().trim() ||
                  $('meta[property="og:title"]').attr('content') ||
                  $('h1.entry-title').text().trim() ||
                  '';

    const images = [];
    $('.reading-content.manga-chapter img, .reading-content img, .page-break img').each((i, el) => {
      const $el = $(el);
      const src = $el.attr('src') ||
                  $el.attr('data-src') ||
                  $el.attr('data-lazy-src') ||
                  $el.attr('data-original') ||
                  $el.attr('data-cfsrc') ||
                  null;
      if (src && !src.startsWith('data:image')) {
        images.push({
          src,
          alt: $el.attr('alt') || null,
          index: i + 1
        });
      }
    });

    if (images.length === 0) {
      $('.page-break, .reading-content div[style*="background"]').each((i, el) => {
        const style = $(el).attr('style');
        if (style) {
          const match = style.match(/url\(['"]?(.*?)['"]?\)/);
          if (match && match[1]) {
            images.push({
              src: match[1],
              alt: null,
              index: i + 1
            });
          }
        }
      });
    }

    let prevChapter = null;
    const prevEl = $('.nav-previous a, .nav-links a:contains("Previous")');
    if (prevEl.length) prevChapter = prevEl.attr('href');

    let nextChapter = null;
    const nextEl = $('.nav-next a, .nav-links a:contains("Next")');
    if (nextEl.length) nextChapter = nextEl.attr('href');

    const chapters = [];
    $('select.paged-links option').each((i, el) => {
      const val = $(el).attr('value');
      const text = $(el).text().trim();
      if (val) {
        chapters.push({
          number: parseChapterNumber(text),
          title: text,
          url: val.startsWith('http') ? val : this.baseUrl + val
        });
      }
    });

    return clean({
      creator: this.creator,
      page: 'chapter',
      data: {
        url,
        title,
        images,
        prevChapter: prevChapter ? (prevChapter.startsWith('http') ? prevChapter : this.baseUrl + prevChapter) : null,
        nextChapter: nextChapter ? (nextChapter.startsWith('http') ? nextChapter : this.baseUrl + nextChapter) : null,
        chapters
      }
    });
  }

  async search(query, page = 1) {
    const url = page === 1
      ? this.baseUrl + `/?s=${encodeURIComponent(query)}&post_type=wp-manga`
      : this.baseUrl + `/page/${page}/?s=${encodeURIComponent(query)}&post_type=wp-manga`;
    return this.list(url);
  }

  async home(page = 1) {
    const url = page === 1 ? this.baseUrl + '/' : this.baseUrl + `/page/${page}/`;
    return this.list(url);
  }

  async series(orderby = 'latest', page = 1) {
    const orderMap = {
      latest: '',
      title: 'title',
      trending: 'trending',
      rating: 'rating',
      views: 'views',
      new: 'new-manga',
      modified: 'modified'
    };
    const order = orderMap[orderby] || '';
    const url = page === 1
      ? this.baseUrl + `/series/${order ? '?m_orderby=' + order : ''}`
      : this.baseUrl + `/series/page/${page}/${order ? '?m_orderby=' + order : ''}`;
    return this.list(url);
  }

  async genre(slug, orderby = 'latest', page = 1) {
    const orderMap = {
      latest: '',
      title: 'title',
      trending: 'trending',
      rating: 'rating',
      views: 'views',
      new: 'new-manga',
      modified: 'modified'
    };
    const order = orderMap[orderby] || '';
    const url = page === 1
      ? this.baseUrl + `/publication-genre/${slug}/${order ? '?m_orderby=' + order : ''}`
      : this.baseUrl + `/publication-genre/${slug}/page/${page}/${order ? '?m_orderby=' + order : ''}`;
    return this.list(url);
  }

  async tag(slug, orderby = 'latest', page = 1) {
    const orderMap = {
      latest: '',
      title: 'title',
      trending: 'trending',
      rating: 'rating',
      views: 'views',
      new: 'new-manga',
      modified: 'modified'
    };
    const order = orderMap[orderby] || '';
    const url = page === 1
      ? this.baseUrl + `/publication-tag/${slug}/${order ? '?m_orderby=' + order : ''}`
      : this.baseUrl + `/publication-tag/${slug}/page/${page}/${order ? '?m_orderby=' + order : ''}`;
    return this.list(url);
  }

  async release(year, orderby = 'latest', page = 1) {
    const orderMap = {
      latest: '',
      title: 'title',
      trending: 'trending',
      rating: 'rating',
      views: 'views',
      new: 'new-manga',
      modified: 'modified'
    };
    const order = orderMap[orderby] || '';
    const url = page === 1
      ? this.baseUrl + `/publication-release/${year}/${order ? '?m_orderby=' + order : ''}`
      : this.baseUrl + `/publication-release/${year}/page/${page}/${order ? '?m_orderby=' + order : ''}`;
    return this.list(url);
  }
}

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

  (async () => {
    let result;
    try {
      switch (command) {
        case 'home':
          result = await scraper.home(parseInt(params[0]) || 1);
          break;
        case 'series':
          result = await scraper.series(params[0] || 'latest', parseInt(params[1]) || 1);
          break;
        case 'search':
          if (!params[0]) throw new Error('Query required');
          result = await scraper.search(params[0], parseInt(params[1]) || 1);
          break;
        case 'genre':
          if (!params[0]) throw new Error('Genre slug required');
          result = await scraper.genre(params[0], params[1] || 'latest', parseInt(params[2]) || 1);
          break;
        case 'tag':
          if (!params[0]) throw new Error('Tag slug required');
          result = await scraper.tag(params[0], params[1] || 'latest', parseInt(params[2]) || 1);
          break;
        case 'release':
          if (!params[0]) throw new Error('Year required');
          result = await scraper.release(params[0], params[1] || 'latest', parseInt(params[2]) || 1);
          break;
        case 'detail':
          if (!params[0]) throw new Error('Slug or URL required');
          result = await scraper.detail(params[0]);
          break;
        case 'chapter':
          if (!params[0]) throw new Error('Slug or URL required');
          result = await scraper.chapter(params[0]);
          break;
        case 'episode-video':
          if (!params[0]) throw new Error('URL required');
          result = await scraper.episodeVideo(params[0]);
          break;
        default:
          console.error('Unknown command');
          console.log(`
Commands:
  home [page]                    - Homepage (latest series)
  series [orderby] [page]        - Series list (orderby: latest, title, trending, rating, views, new, modified)
  search <query> [page]          - Search manga
  genre <slug> [orderby] [page]  - Filter by genre (e.g. uncensored)
  tag <slug> [orderby] [page]    - Filter by tag (e.g. tsundere)
  release <year> [orderby] [page] - Filter by release year (e.g. 2026)
  detail <slug|url>              - Get series detail + chapter list
  chapter <url>                  - Get chapter images (auto-detects video)
  episode-video <url>            - Force video extraction
          `);
          process.exit(1);
      }
      console.log(JSON.stringify(result, null, 2));
    } catch (err) {
      console.error(JSON.stringify({ error: err.message }));
      process.exit(1);
    }
  })();
}

module.exports = MangaDistrictScraper;

Rating

β€”(0)

Gimana snippet ini menurutmu?

</> Embed

Embed ke website / blog

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

Related Snippets

manga18

Fitur: Β· home, list, completed, manhwaRaw, genre, search, author, artist, detail, chapter Β· Pagination + filter (latest, alphabet, rating, trending) Β· Chapter list lengkap (semua chapter per slug) Β· Rating fix di semua halaman Β· 20+ User-Agent, retry, proxy support

5
9