Loading snippets...
/**
* Lk21 movie + drama
* base: https://tv10.lk21official.cc/
* Creator: ShanMolvyr
* Jangan Hapus Kreator woi ^_^
*
*
* Note: cek https://snippet.vyr.my.id/shanmolvyr/lk21/README.md
* Sumber: https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q
*/
const axios = require('axios');
const cheerio = require('cheerio');
const DOMAINS = {
lk21: 'https://tv10.lk21official.cc',
nontondrama: 'https://tv4.nontondrama.my',
};
const HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Cache-Control': 'no-cache',
};
async function fetchPage(url, referer) {
const res = await axios.get(url, {
headers: { ...HEADERS, Referer: referer || url },
timeout: 15000,
maxRedirects: 5,
});
return cheerio.load(res.data);
}
function parseList($) {
const results = [];
$('article').each((_, el) => {
const $el = $(el);
const $a = $el.find('figure a').first();
const href = $a.attr('href') || '';
if (!href) return;
const title = $el.find('h3.poster-title, h2.poster-title').first().text().trim() || $a.attr('title') || '';
const poster = $el.find('source[type="image/jpeg"]').attr('srcset') || $el.find('img').attr('data-src') || $el.find('img').attr('src') || '';
const year = $el.find('span.year').text().trim() || '';
const quality = $el.find('span.label').text().trim() || '';
const rating = $el.find('span[itemprop="ratingValue"]').text().trim() || '';
const episode = $el.find('span.episode strong').text().trim() || '';
const duration = $el.find('span.duration').text().trim() || '';
results.push({ title, href, poster, year, quality, rating, episode, duration });
});
return results;
}
function getYear($) {
let year = '';
$('script[type="application/ld+json"]').each((_, el) => {
if (year) return;
const m = ($(el).html() || '').match(/"datePublished":\s*"(\d{4})/);
if (m) year = m[1];
});
if (!year) $('.tag-list a[href*="/year/"]').each((_, el) => { year = $(el).text().trim(); });
return year;
}
function getTags($) {
const tags = [];
$('.tag-list .tag a').each((_, el) => tags.push({ label: $(el).text().trim(), href: $(el).attr('href') || '' }));
return {
genre: tags.filter(t => t.href.includes('/genre/')).map(t => t.label),
country: tags.filter(t => t.href.includes('/country/')).map(t => t.label),
};
}
function parseMovieDetail($) {
const title = $('h1').first().text().trim();
const rating = ($('.info-tag span strong').first().text().trim()).replace(/[^\d.]/g, '');
const infoSpans = [];
$('.info-tag span').each((_, el) => { const t = $(el).text().trim(); if (t) infoSpans.push(t); });
const { genre, country } = getTags($);
const synopsis = $('[data-full]').first().attr('data-full') || '';
const poster = $('meta[property="og:image"]').attr('content') || '';
const servers = [];
const seen = new Set();
$('[data-server]').each((_, el) => {
const server = $(el).attr('data-server'), url = $(el).attr('data-url');
if (server && url && !seen.has(server)) { seen.add(server); servers.push({ server, url }); }
});
return { title, rating, quality: infoSpans[1] || '', resolution: infoSpans[2] || '', duration: infoSpans[3] || '', year: getYear($), genre, country, synopsis, poster, servers };
}
function parseSeriesDetail($) {
const title = $('h1').first().text().trim();
const rating = ($('.info-tag span strong').first().text().trim()).replace(/[^\d.]/g, '');
const infoSpans = [];
$('.info-tag span').each((_, el) => { const t = $(el).text().trim(); if (t) infoSpans.push(t); });
const { genre, country } = getTags($);
const synopsis = $('[data-full]').first().attr('data-full') || '';
const poster = $('meta[property="og:image"]').attr('content') || '';
let episodes = [];
$('script').each((_, el) => {
const txt = $(el).html() || '';
const m = txt.match(/^\s*(\{"1":\[.*\].*\})\s*$/);
if (m) {
try {
const data = JSON.parse(m[1]);
Object.values(data).forEach(season => season.forEach(ep => {
episodes.push({ episode: ep.episode_no, season: ep.s, title: ep.title, slug: ep.slug, href: `/${ep.slug}` });
}));
} catch (_) {}
}
});
if (episodes.length === 0) {
$('.episode-list a').each((_, el) => {
const href = $(el).attr('href') || '', label = $(el).text().trim();
if (href && href.includes('episode')) episodes.push({ label, href });
});
}
return { title, rating, airDate: infoSpans[1] || '', type: infoSpans[2] || '', status: infoSpans[3] || '', year: getYear($), genre, country, synopsis, poster, episodes };
}
function parseEpisodeWatch($) {
const title = $('h1').first().text().trim();
let meta = {};
$('script').each((_, el) => {
const txt = $(el).html() || '';
const m = txt.match(/\{[^<]*"current_eps"[^<]*\}/);
if (m) { try { meta = JSON.parse(m[0]); } catch (_) {} }
});
const servers = [];
const seen = new Set();
$('[data-server]').each((_, el) => {
const server = $(el).attr('data-server'), url = $(el).attr('data-url');
if (server && url && !seen.has(server)) { seen.add(server); servers.push({ server, url }); }
});
let prevEp = null;
const nextEp = meta.next ? `/${meta.next}` : null;
if (meta.current_eps > 1 && meta.slug) {
prevEp = `/${meta.slug.replace(/-episode-\d+-/, `-episode-${meta.current_eps - 1}-`)}`;
}
return { title, season: meta.current_season || null, episode: meta.current_eps || null, totalEps: meta.total_eps || null, rating: meta.rating || null, poster: meta.poster || null, seriesSlug: meta.slug || null, servers, prevEp, nextEp };
}
const api = {
async home() { const $ = await fetchPage(`${DOMAINS.lk21}/`); return { source: DOMAINS.lk21, data: parseList($) }; },
async latest(page = 1) { const url = page > 1 ? `${DOMAINS.lk21}/latest/page/${page}/` : `${DOMAINS.lk21}/latest/`; const $ = await fetchPage(url); return { page, data: parseList($) }; },
async search(q, page = 1) { const b = `${DOMAINS.lk21}/search/`; const url = page > 1 ? `${b}page/${page}/?s=${encodeURIComponent(q)}` : `${b}?s=${encodeURIComponent(q)}`; const $ = await fetchPage(url); return { query: q, page, data: parseList($) }; },
async genre(genre, page = 1) { const b = `${DOMAINS.lk21}/genre/${genre}/`; const url = page > 1 ? `${b}page/${page}/` : b; const $ = await fetchPage(url); return { genre, page, data: parseList($) }; },
async detail(slug) {
const url = `${DOMAINS.lk21}/${slug}/`;
const $ = await fetchPage(url, DOMAINS.lk21);
const h1 = $('h1').first().text().toLowerCase();
if (h1.includes('dialihkan') || h1.includes('nontondrama')) {
const sUrl = `${DOMAINS.nontondrama}/${slug}/`;
const $s = await fetchPage(sUrl, DOMAINS.nontondrama);
return { url: sUrl, type: 'series', data: parseSeriesDetail($s) };
}
return { url, type: 'movie', data: parseMovieDetail($) };
},
async seriesHome() { const $ = await fetchPage(`${DOMAINS.nontondrama}/`); return { source: DOMAINS.nontondrama, data: parseList($) }; },
async seriesLatest(page = 1) { const url = page > 1 ? `${DOMAINS.nontondrama}/latest-series/page/${page}/` : `${DOMAINS.nontondrama}/latest-series/`; const $ = await fetchPage(url); return { page, data: parseList($) }; },
async seriesSearch(q, page = 1) { const b = `${DOMAINS.nontondrama}/search/`; const url = page > 1 ? `${b}page/${page}/?s=${encodeURIComponent(q)}` : `${b}?s=${encodeURIComponent(q)}`; const $ = await fetchPage(url); return { query: q, page, data: parseList($) }; },
async seriesGenre(genre, page = 1) { const b = `${DOMAINS.nontondrama}/genre/${genre}/`; const url = page > 1 ? `${b}page/${page}/` : b; const $ = await fetchPage(url); return { genre, page, data: parseList($) }; },
async seriesDetail(slug) {
const url = `${DOMAINS.nontondrama}/${slug}/`;
const $ = await fetchPage(url, DOMAINS.nontondrama);
const ogType = $('meta[property="og:type"]').attr('content') || '';
const ogUrl = $('meta[property="og:url"]').attr('content') || '';
const isMovie = ogType !== 'series' && (ogUrl.includes('d21.team') || ogUrl.includes('lk21official') || !ogUrl.includes('nontondrama'));
if (isMovie) {
const mUrl = `${DOMAINS.lk21}/${slug}/`;
const $m = await fetchPage(mUrl, DOMAINS.lk21);
return { url: mUrl, type: 'movie', data: parseMovieDetail($m) };
}
return { url, type: 'series', data: parseSeriesDetail($) };
},
async seriesWatch(slug) { const url = `${DOMAINS.nontondrama}/${slug}/`; const $ = await fetchPage(url, DOMAINS.nontondrama); return { url, data: parseEpisodeWatch($) }; },
async searchAll(q) {
const [a, b] = await Promise.allSettled([api.search(q), api.seriesSearch(q)]);
return { query: q, movies: a.status === 'fulfilled' ? a.value : { error: a.reason?.message }, series: b.status === 'fulfilled' ? b.value : { error: b.reason?.message } };
},
};
function startServer() {
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
const h = (fn) => async (req, res) => {
try { res.json({ ok: true, ...(await fn(req)) }); }
catch (err) { res.status(500).json({ ok: false, error: err.message }); }
};
app.get('/', (_, res) => res.json({ name: 'lk21-api', v: '1.0.0', by: '@SanVyr', endpoints: ['GET /movie/home','GET /movie/latest?page=','GET /movie/search?q=&page=','GET /movie/genre/:genre?page=','GET /movie/detail?slug= (auto-detect movie/series)','GET /series/home','GET /series/latest?page=','GET /series/search?q=&page=','GET /series/genre/:genre?page=','GET /series/detail?slug=','GET /series/watch?slug=','GET /search?q='] }));
app.get('/movie/home', h(() => api.home()));
app.get('/movie/latest', h(r => api.latest(+r.query.page || 1)));
app.get('/movie/search', h(r => { if (!r.query.q) throw new Error('?q= required'); return api.search(r.query.q, +r.query.page || 1); }));
app.get('/movie/genre/:genre', h(r => api.genre(r.params.genre, +r.query.page || 1)));
app.get('/movie/detail', h(r => { if (!r.query.slug) throw new Error('?slug= required'); return api.detail(r.query.slug); }));
app.get('/series/home', h(() => api.seriesHome()));
app.get('/series/latest', h(r => api.seriesLatest(+r.query.page || 1)));
app.get('/series/search', h(r => { if (!r.query.q) throw new Error('?q= required'); return api.seriesSearch(r.query.q, +r.query.page || 1); }));
app.get('/series/genre/:genre', h(r => api.seriesGenre(r.params.genre, +r.query.page || 1)));
app.get('/series/detail', h(r => { if (!r.query.slug) throw new Error('?slug= required'); return api.seriesDetail(r.query.slug); }));
app.get('/series/watch', h(r => { if (!r.query.slug) throw new Error('?slug= required'); return api.seriesWatch(r.query.slug); }));
app.get('/search', h(r => { if (!r.query.q) throw new Error('?q= required'); return api.searchAll(r.query.q); }));
app.use((_, res) => res.status(404).json({ ok: false, error: 'Not found' }));
app.listen(PORT, () => console.error(`lk21-api :${PORT}`));
}
async function cli() {
const [,, cmd, ...args] = process.argv;
function parseQueryPage(args) {
const lastIsPage = args.length > 1 && /^\d+$/.test(args[args.length - 1]);
return { query: (lastIsPage ? args.slice(0, -1) : args).join(' '), page: lastIsPage ? +args[args.length - 1] : 1 };
}
const commands = {
'home': () => api.home(),
'latest': () => api.latest(+args[0] || 1),
'search': () => { const { query, page } = parseQueryPage(args); return api.search(query, page); },
'genre': () => api.genre(args[0], +args[1] || 1),
'detail': () => api.detail(args[0]),
'series:home': () => api.seriesHome(),
'series:latest': () => api.seriesLatest(+args[0] || 1),
'series:search': () => { const { query, page } = parseQueryPage(args); return api.seriesSearch(query, page); },
'series:genre': () => api.seriesGenre(args[0], +args[1] || 1),
'series:detail': () => api.seriesDetail(args[0]),
'series:watch': () => api.seriesWatch(args[0]),
'search:all': () => { const { query } = parseQueryPage(args); return api.searchAll(query); },
'server': () => { startServer(); return null; },
};
if (!cmd || !commands[cmd]) { process.exit(1); }
const result = await commands[cmd]();
if (result !== null) console.log(JSON.stringify({ ok: true, ...result }, null, 2));
}
cli().catch(err => { console.log(JSON.stringify({ ok: false, error: err.message }, null, 2)); process.exit(1); });
Jika kamu seorang programmer, developer, atau creator, seharusnya kamu paham bahwa membuat sesuatu membutuhkan waktu, usaha, dan proses yang tidak sebentar.
Saya tahu sebagian data atau konten di sini berasal dari proses scraping sumber lain. Namun sistem, kode, pengembangan, pemeliharaan, perbaikan bug, dan berbagai usaha di balik proyek ini tetap saya kerjakan sendiri.
Jika kamu melakukan reupload, scraping ulang, menjadikannya API, atau menggunakan bagian dari proyek ini, mohon sertakan sumber asli:
«Memberikan kredit tidak mengurangi nilai karyamu. Menghilangkan sumber tidak membuat karya ini berasal darimu.»
bashscraper lk21 movie dan nontondrama lengkap
bashnode lk21.js home node lk21.js latest node lk21.js latest 2 node lk21.js search still hope node lk21.js search still hope 2 node lk21.js genre action node lk21.js genre romance 2 node lk21.js detail still-hope-2026 node lk21.js detail divorce-attorney-shin-2023 node lk21.js series:home node lk21.js series:latest node lk21.js series:latest 2 node lk21.js series:search breaking bad node lk21.js series:genre drama node lk21.js series:detail wonderfools-2026 node lk21.js series:watch wonderfools-season-1-episode-1-2026 node lk21.js search:all avengers node lk21.js server
bashby ShanMolvyr