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

18+

rule34video

kalo ada bug benerin sendiri.

Creator

qrtz

Language

javascript

Views

25

Copies

5

Base

https://rule34video.com/

Updated

24 Jun 2026

#18+#scraper

Code

255
rule34video.js
const cloudscraper = require('cloudscraper');
const cheerio = require('cheerio');

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

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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  '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'
];

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',
    'Accept-Language': 'en-US,en;q=0.9,id;q=0.8',
    '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'
  };
}

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;
}

async function fetchHTML(url, referer = null) {
  try {
    const response = await cloudscraper.get({
      uri: url,
      headers: getHeaders(referer || url),
      timeout: 30000,
      followRedirect: true,
      maxRedirects: 5,
      gzip: true,
      challenge: true
    });
    return response;
  } catch (err) {
    if (err.response && err.response.statusCode === 403) {
      throw new Error('Access denied (403)');
    }
    throw err;
  }
}

function extractPagination($) {
  let next = null;
  const nextEl = $('.pagination .next, .next a, a:contains("Next"), a[rel="next"]');
  if (nextEl.length) {
    const href = nextEl.attr('href');
    if (href) next = href;
  }
  if (!next) {
    const lastPage = $('.pagination .item:not(.active) a').last();
    if (lastPage.length) {
      const href = lastPage.attr('href');
      if (href) next = href;
    }
  }
  return next;
}

function extractVideoSources(html) {
  const sources = {};
  const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
  let match;
  while ((match = scriptRegex.exec(html)) !== null) {
    const scriptContent = match[1];
    const patterns = {
      '360p': /video_url\s*[:=]\s*["']([^"']+)["']/,
      '480p': /video_alt_url\s*[:=]\s*["']([^"']+)["']/,
      '720p': /video_alt_url2\s*[:=]\s*["']([^"']+)["']/,
      '1080p': /video_alt_url3\s*[:=]\s*["']([^"']+)["']/
    };
    for (const [quality, pattern] of Object.entries(patterns)) {
      const m = scriptContent.match(pattern);
      if (m && m[1]) {
        sources[quality] = decodeURIComponent(m[1]);
      }
    }
  }
  if (Object.keys(sources).length === 0) {
    const linkRegex = /href=["']([^"']*get_file[^"']*\.mp4[^"']*)["']/gi;
    let linkMatch;
    while ((linkMatch = linkRegex.exec(html)) !== null) {
      const url = linkMatch[1];
      let quality = 'unknown';
      if (url.includes('_1080p')) quality = '1080p';
      else if (url.includes('_720p')) quality = '720p';
      else if (url.includes('_480p')) quality = '480p';
      else if (url.includes('_360p') || url.includes('_360.mp4')) quality = '360p';
      if (!sources[quality]) {
        sources[quality] = url;
      }
    }
  }
  const thumbMatch = html.match(/preview_url\s*[:=]\s*["']([^"']+)["']/);
  if (thumbMatch) {
    sources['thumbnail'] = thumbMatch[1];
  } else {
    const jsonLdMatch = html.match(/<script[^>]*type\s*=\s*["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/);
    if (jsonLdMatch) {
      try {
        const json = JSON.parse(jsonLdMatch[1]);
        if (json && json.thumbnailUrl) {
          sources['thumbnail'] = json.thumbnailUrl;
        }
      } catch(e) {}
    }
  }
  return sources;
}

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

  async search(query, page = 1, limit = 20) {
    const url = page === 1
      ? this.baseUrl + `/search/${encodeURIComponent(query)}/`
      : this.baseUrl + `/search/${encodeURIComponent(query)}/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb, .thumb-block, .video-thumb').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/video/"]').first();
      const link = linkEl.attr('href');
      const title = linkEl.attr('title') || linkEl.find('img').attr('alt') || '';
      const poster = $el.find('img').first().attr('data-original') || $el.find('img').first().attr('src') || null;
      if (link) {
        items.push({ title, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'search',
      data: { query, url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async popular(page = 1, limit = 20) {
    const url = page === 1 ? this.baseUrl + '/most-popular/' : this.baseUrl + `/most-popular/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb, .thumb-block, .video-thumb').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/video/"]').first();
      const link = linkEl.attr('href');
      const title = linkEl.attr('title') || linkEl.find('img').attr('alt') || '';
      const poster = $el.find('img').first().attr('data-original') || $el.find('img').first().attr('src') || null;
      if (link) {
        items.push({ title, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'popular',
      data: { url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async detail(url) {
    const fullUrl = url.startsWith('http') ? url : this.baseUrl + url;
    const html = await fetchHTML(fullUrl);
    const $ = cheerio.load(html);
    const videoIdMatch = fullUrl.match(/\/video\/(\d+)/);
    const videoId = videoIdMatch ? videoIdMatch[1] : null;
    const title = $('h1.title_video, h1, .title, .video-title').first().text().trim() ||
                  $('meta[property="og:title"]').attr('content') ||
                  $('title').text().replace(' - Rule34Video', '').trim() || '';
    const poster = $('meta[property="og:image"]').attr('content') ||
                   $('.poster img, .video-poster img').first().attr('src') || null;
    const description = $('meta[name="description"]').attr('content') ||
                        $('.desc, .description, .video-desc').first().text().trim() || null;
    let views = null;
    const viewsEl = $('.item_info svg.custom-eye ~ span, .views, .view-count, .video-views').first();
    if (viewsEl.length) {
      const viewsText = viewsEl.text().trim();
      const match = viewsText.match(/([\d,.]+)/);
      if (match) views = parseInt(match[1].replace(/,/g, ''));
    }
    let likes = null;
    let likePercent = null;
    const likesEl = $('.voters.count, .likes, .rating').first();
    if (likesEl.length) {
      const likesText = likesEl.text().trim();
      const percentMatch = likesText.match(/(\d+)%/);
      if (percentMatch) likePercent = parseInt(percentMatch[1]);
      const countMatch = likesText.match(/(\d+[\d,]*)/);
      if (countMatch) likes = parseInt(countMatch[1].replace(/,/g, ''));
    }
    let duration = null;
    const durationEl = $('.item_info svg.custom-time ~ span, .duration, .video-duration').first();
    if (durationEl.length) duration = durationEl.text().trim();
    let uploadDate = null;
    const dateEl = $('.item_info svg.custom-calendar ~ span, .upload-date, .date').first();
    if (dateEl.length) uploadDate = dateEl.text().trim();
    const categories = [];
    $('.col[data-suggest-type="category"] .video_meta_pill .name, .category-item a, .video-category').each((i, el) => {
      const cat = $(el).text().trim();
      if (cat) categories.push(cat);
    });
    const artists = [];
    $('.col[data-suggest-type="model"] .video_meta_pill .name, .artist-item a, .video-artist').each((i, el) => {
      const artist = $(el).text().trim();
      if (artist) artists.push(artist);
    });
    let uploader = null;
    const uploaderEl = $('.uploader, .user, .username, .member-link').first();
    if (uploaderEl.length) uploader = uploaderEl.text().trim();
    const tags = [];
    $('.wrap[data-suggest-type="tag"] .tag_item, .tag-item a, a[href*="/tags/"]').each((i, el) => {
      const tag = $(el).text().trim();
      if (tag && tag.length > 1 && !tag.includes('#')) {
        tags.push(tag);
      }
    });
    const videoSources = extractVideoSources(html);
    const downloadLinks = [];
    $('a[href*="download=true"]').each((i, el) => {
      const href = $(el).attr('href');
      if (href) {
        let quality = 'unknown';
        if (href.includes('_1080p')) quality = '1080p';
        else if (href.includes('_720p')) quality = '720p';
        else if (href.includes('_480p')) quality = '480p';
        else if (href.includes('_360p') || href.includes('_360.mp4')) quality = '360p';
        downloadLinks.push({ quality, url: href });
      }
    });
    if (downloadLinks.length === 0) {
      for (const [quality, url] of Object.entries(videoSources)) {
        if (quality !== 'thumbnail' && url.includes('get_file')) {
          const downloadUrl = url.includes('?') ? url + '&download=true' : url + '?download=true';
          downloadLinks.push({ quality, url: downloadUrl });
        }
      }
    }
    return clean({
      creator: this.creator,
      page: 'detail',
      data: {
        url: fullUrl,
        videoId,
        title,
        poster,
        description,
        views,
        likes,
        likePercent,
        duration,
        uploadDate,
        categories,
        artists,
        uploader,
        tags,
        videoSources,
        downloadLinks,
        hasVideo: Object.keys(videoSources).length > 0
      }
    });
  }

  async models(page = 1, limit = 50) {
    const url = page === 1 ? this.baseUrl + '/models/' : this.baseUrl + `/models/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.list_items .item, .model-item, .model-block').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/models/"]').first();
      const link = linkEl.attr('href');
      const name = linkEl.find('.name').text().trim() || linkEl.attr('title') || '';
      const poster = $el.find('img').first().attr('src') || null;
      const rating = $el.find('.count span').text().trim() || null;
      if (link && name) {
        items.push({ name, url: link.startsWith('http') ? link : this.baseUrl + link, poster, rating });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'models',
      data: { url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async modelDetail(slug, page = 1, limit = 20) {
    const url = page === 1 ? this.baseUrl + `/models/${slug}/` : this.baseUrl + `/models/${slug}/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb, .thumb-block, .video-thumb').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/video/"]').first();
      const link = linkEl.attr('href');
      const title = linkEl.attr('title') || linkEl.find('img').attr('alt') || '';
      const poster = $el.find('img').first().attr('data-original') || $el.find('img').first().attr('src') || null;
      if (link) {
        items.push({ title, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'model_detail',
      data: { slug, url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async tags(page = 1, limit = 50) {
    const url = page === 1 ? this.baseUrl + '/tags/' : this.baseUrl + `/tags/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.tags-holder .column .item, .tag-item, .tag-block').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a').first();
      const link = linkEl.attr('href');
      const name = linkEl.contents().filter((_, node) => node.type === 'text').text().trim() || linkEl.attr('title') || '';
      const countText = linkEl.find('span').text().trim() || '';
      const count = countText.replace(/,/g, '').trim();
      if (link && name) {
        items.push({ name, count: count ? parseInt(count) : null, url: link.startsWith('http') ? link : this.baseUrl + link });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'tags',
      data: { url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async tagDetail(slug, page = 1, limit = 20) {
    const url = page === 1 ? this.baseUrl + `/tags/${slug}/` : this.baseUrl + `/tags/${slug}/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb, .thumb-block, .video-thumb').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/video/"]').first();
      const link = linkEl.attr('href');
      const title = linkEl.attr('title') || linkEl.find('img').attr('alt') || '';
      const poster = $el.find('img').first().attr('data-original') || $el.find('img').first().attr('src') || null;
      if (link) {
        items.push({ title, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'tag_detail',
      data: { slug, url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }

  async categories(limit = 50) {
    const url = this.baseUrl + '/categories/';
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb.item, .category-item, .cat-item').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a.th, a[href*="/categories/"]').first();
      const link = linkEl.attr('href');
      const name = linkEl.find('.thumb_title').text().trim() || linkEl.attr('title') || '';
      const poster = $el.find('img').first().attr('src') || null;
      if (link && name) {
        items.push({ name, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    return clean({
      creator: this.creator,
      page: 'categories',
      data: { url, count: items.length, items }
    });
  }

  async categoryDetail(slug, page = 1, limit = 20) {
    const url = page === 1 ? this.baseUrl + `/categories/${slug}/` : this.baseUrl + `/categories/${slug}/${page}/`;
    const html = await fetchHTML(url);
    const $ = cheerio.load(html);
    const items = [];
    $('.thumb, .thumb-block, .video-thumb').each((i, el) => {
      if (limit && i >= limit) return false;
      const $el = $(el);
      const linkEl = $el.find('a[href*="/video/"]').first();
      const link = linkEl.attr('href');
      const title = linkEl.attr('title') || linkEl.find('img').attr('alt') || '';
      const poster = $el.find('img').first().attr('data-original') || $el.find('img').first().attr('src') || null;
      if (link) {
        items.push({ title, url: link.startsWith('http') ? link : this.baseUrl + link, poster });
      }
    });
    const next = extractPagination($);
    return clean({
      creator: this.creator,
      page: 'category_detail',
      data: { slug, url, count: items.length, currentPage: page, items, next: next ? (next.startsWith('http') ? next : this.baseUrl + next) : null }
    });
  }
}

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

  (async () => {
    let result;
    try {
      switch (command) {
        case 'search':
          if (!params[0]) throw new Error('Query required');
          result = await scraper.search(params[0], parseInt(params[1]) || 1, parseInt(params[2]) || 20);
          break;
        case 'popular':
          result = await scraper.popular(parseInt(params[0]) || 1, parseInt(params[1]) || 20);
          break;
        case 'detail':
          if (!params[0]) throw new Error('URL required');
          result = await scraper.detail(params[0]);
          break;
        case 'models':
          result = await scraper.models(parseInt(params[0]) || 1, parseInt(params[1]) || 50);
          break;
        case 'model':
          if (!params[0]) throw new Error('Model slug required');
          result = await scraper.modelDetail(params[0], parseInt(params[1]) || 1, parseInt(params[2]) || 20);
          break;
        case 'tags':
          result = await scraper.tags(parseInt(params[0]) || 1, parseInt(params[1]) || 50);
          break;
        case 'tag':
          if (!params[0]) throw new Error('Tag slug required');
          result = await scraper.tagDetail(params[0], parseInt(params[1]) || 1, parseInt(params[2]) || 20);
          break;
        case 'categories':
          result = await scraper.categories(parseInt(params[0]) || 50);
          break;
        case 'category':
          if (!params[0]) throw new Error('Category slug required');
          result = await scraper.categoryDetail(params[0], parseInt(params[1]) || 1, parseInt(params[2]) || 20);
          break;
        default:
          console.error(`Unknown command: ${command}`);
          console.log(`
Commands:
  search <query> [page] [limit]      - Search videos
  popular [page] [limit]             - Most popular videos
  detail <url>                       - Get video detail + download links
  models [page] [limit]              - List models
  model <slug> [page] [limit]        - Videos by model
  tags [page] [limit]                - List tags
  tag <slug> [page] [limit]          - Videos by tag
  categories [limit]                 - List categories
  category <slug> [page] [limit]     - Videos in category
          `);
          process.exit(1);
      }
      console.log(JSON.stringify(result, null, 2));
    } catch (err) {
      console.error(JSON.stringify({ error: err.message }));
      process.exit(1);
    }
  })();
}

module.exports = Rule34VideoScraper;

Rating

—(0)

Gimana snippet ini menurutmu?

</> Embed

Embed ke website / blog

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

Related Snippets

hentaimama

goon

25
4

rule34video-serverless-supp

serverless supp ga perlu pake cloudscraper lagi

15
4

xanimu

goon.

12
1