UNPKG

@kelvdra/scraper

Version:

A simple scraper by kelvdra.

635 lines (567 loc) 17.3 kB
const axios = require('axios'); const yts = require('yt-search'); const { createDecipheriv } = require('crypto'); const cheerio = require('cheerio'); const formData = require('form-data'); const { lookup } = require('mime-types'); const qs = require('qs'); function formatNumber(num) { if (num >= 1000000) return (num / 1000000).toFixed(2) + ' jt'; if (num >= 1000) return (num / 1000).toFixed(2) + ' rb'; return num.toString(); } function formatDate(timestamp) { const date = new Date(timestamp * 1000); return date.toLocaleString('id-ID', { weekday: 'long', day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } /** * @param {string} url - URL YouTube * @param {string} quality - Bisa resolusi (360, 480, 720, 1080) atau bitrate (128, 192) */ // --- SSVID async function ssvidDownloader(url, quality = '360') { try { if (!/^https:\/\/(www\.)?(youtube\.com|youtu\.be)\//.test(url)) { throw new Error('URL tidak valid'); } const isAudio = ['64', '96', '128', '160', '192', '256', '320'].includes(quality); const type = isAudio ? 'audio' : 'video'; const searchRes = await axios.post( 'https://ssvid.net/api/ajax/search', qs.stringify({ query: url, vt: 'home' }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', }, } ); const searchData = searchRes.data; if (!searchData || searchData.status !== 'ok') { throw new Error('Gagal mengambil data video'); } const videos = searchData.links?.mp4 || {}; const audios = searchData.links?.mp3 || {}; const list = isAudio ? Object.entries(audios).map(([k, v]) => ({ quality: v.q.replace('kbps', ''), size: v.size, format: v.f, k: v.k, })) : Object.entries(videos).map(([k, v]) => ({ quality: v.q.replace('p', ''), size: v.size, format: v.f, k: v.k, })); const selected = list.find((x) => x.quality === quality) || list[0]; if (!selected || !selected.k) throw new Error('Format yang diminta tidak tersedia'); const convertRes = await axios.post( 'https://ssvid.net/api/ajax/convert', qs.stringify({ vid: searchData.vid, k: selected.k }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://ssvid.net/', 'User-Agent': 'Mozilla/5.0', }, } ); const response = convertRes.data; const downloadUrl = response?.dlink || response?.url; if (!downloadUrl) throw new Error('Gagal mengonversi media'); return { status: true, type, quality: `${quality}${isAudio ? 'kbps' : 'p'}`, url: downloadUrl, }; } catch (err) { return { status: false, message: err.message || 'Terjadi kesalahan', }; } } // --- YT5S async function yt5sDownloader(videoUrl, quality = '720p') { const formatMap = { '360p': '134', '480p': '135', '720p': '136', '1080p': '137', '1440p': '400', '2160p': '401', '128k': '140' }; const format = formatMap[quality] || '136'; const data = { platform: 'youtube', url: videoUrl, title: '', id: 'MpUI2WIOd1JRHCEzpUAVz6K2T504wJ6a1QmXxUPNUTeUbEYH92ehW+4bV8+cy37Q4OAPwxKFOPwWgTuS93pyvMQyyTeRxqp63zrun0tgLIA=', ext: 'mp4', note: quality, format, }; try { const res = await axios.post('https://yt5s.biz/mates/en/convert', qs.stringify(data), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', Accept: 'application/json, text/javascript, */*; q=0.01', 'X-Requested-With': 'XMLHttpRequest', 'x-note': quality, }, }); if (res.data.status === 'success') { return { status: true, type: 'video', quality, url: res.data.downloadUrlX, }; } else { throw new Error('YT5S gagal memberikan download URL.'); } } catch (err) { return { status: false, message: err.message || 'YT5S gagal', }; } } // --- Smart Wrapper (gabungan) async function getYoutubeDownloadUrl(videoUrl, quality = '720p') { const q = quality.toLowerCase().replace('p', '').replace('kbps', ''); const isAudio = ['64', '96', '128', '160', '192', '256', '320'].includes(q); const is360p = q === '360'; if (is360p || isAudio) { return await ssvidDownloader(videoUrl, q); // gunakan ssvid } else { return await yt5sDownloader(videoUrl, `${q}p`); // gunakan yt5s } } const ytmp3 = async (link, quality = "128") => { try { const info = await yts(link); const result = await getYoutubeDownloadUrl(link, quality); return { status: true, creator: '@kelvdra/scraper', metadata: info.all[0], download: result }; } catch (e) { return { status: false, message: e.message }; } }; const ytmp4 = async (link, quality = "360") => { if (!link.includes('youtube.com') && !link.includes('youtu.be')) { return { status: false, message: 'URL YouTube tidak valid' }; } try { const info = await yts(link); const data = await getYoutubeDownloadUrl(link, quality); return { status: true, creator: '@kelvdra/scraper', metadata: info.all[0], download: data }; } catch (e) { return { status: false, message: e.message }; } }; const transcript = async (url) => { try { let res = await axios.get('https://yts.kooska.xyz/', { params: { url: url }, headers: { 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36', 'Referer': 'https://kooska.xyz/' } }).then(i=>i.data) return { status: true, creator: "@kelvdra/scraper", video_id: res.video_id, summarize: res.ai_response, transcript: res.transcript } } catch(e) { return { status: false, msg: `Gagal mendapatkan respon, dengan pesan: ${e.message}` } } } const playmp3 = async (query, quality = 128) => { try { const searchResult = await search(query); if (!searchResult.status || !searchResult.results.length) return { status: false, message: 'Video tidak ditemukan' }; const results = []; for (let video of searchResult.results.slice(0, 5)) { const downloadInfo = await getYoutubeDownloadUrl(video.url, quality) results.push({ title: video.title, author: video.author.name, duration: video.timestamp, url: video.url, thumbnail: video.thumbnail, download: downloadInfo }); } return { status: true, creator: '@kelvdra/scraper', type: 'audio', results }; } catch (err) { return { status: false, message: err.message }; } }; const playmp4 = async (query, quality = 360) => { try { const searchResult = await search(query); if (!searchResult.status || !searchResult.results.length) return { status: false, message: 'Video tidak ditemukan' }; const results = []; for (let video of searchResult.results.slice(0, 5)) { const downloadInfo = await getYoutubeDownloadUrl(video.url, quality); results.push({ title: video.title, author: video.author.name, duration: video.timestamp, url: video.url, thumbnail: video.thumbnail, download: downloadInfo }); } return { status: true, creator: '@kelvdra/scraper', type: 'video', results }; } catch (err) { return { status: false, message: err.message }; } }; const ttdl = async (url) => { try { const postData = qs.stringify({ url: url, count: 12, cursor: 0, web: 1, hd: 1 }); const headers = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'X-Requested-With': 'XMLHttpRequest' }; const res = await axios.post('https://www.tikwm.com/api/', postData, { headers }); const data = res.data.data; const videoId = data.id; const isSlide = Array.isArray(data.images) && data.images.length > 0; const result = { status: true, creator: '@kelvdra/scraper', result: { title: data.title, taken_at: formatDate(data.create_time), region: data.region, id: videoId, durations: data.duration || 0, duration: (data.duration ? `${data.duration} Seconds` : 'Photo Slide'), cover: `https://www.tikwm.com/video/cover/${videoId}.webp`, size_nowm: data.size || 0, size_nowm_hd: data.hd_size || 0, data: isSlide ? data.images.map((url, i) => ({ type: `slide_${i + 1}`, url: url })) : [ { type: "nowatermark", url: `https://www.tikwm.com/video/media/play/${videoId}.mp4` }, { type: "nowatermark_hd", url: `https://www.tikwm.com/video/media/hdplay/${videoId}.mp4` } ], music_info: { id: data.music_info?.id || "", title: data.music_info?.title || "-", author: data.music_info?.author || "-", album: data.music_info?.album || "Unknown", url: `https://www.tikwm.com/video/music/${videoId}.mp3` }, stats: { views: formatNumber(data.play_count), likes: formatNumber(data.digg_count), comment: formatNumber(data.comment_count), share: formatNumber(data.share_count), download: formatNumber(data.download_count) }, author: { id: data.author?.id || "", fullname: data.author?.nickname || "", nickname: data.author?.unique_id || "", avatar: `https://www.tikwm.com/video/avatar/${videoId}.jpeg` } } }; return result } catch (err) { return { status: false, message: err.message }; } } const pindl = async (url) => { try { let a = await axios.get(url, { headers: { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SAMSUNG SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/17.0 Chrome/96.0.4664.104 Mobile Safari/537.36", 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7', } }); let $ = cheerio.load(a.data); let x = $('script[data-test-id="leaf-snippet"]').text(); let y = $('script[data-test-id="video-snippet"]').text(); let g = { status: true, creator: "@kelvdra/scraper", isVideo: !!y, info: JSON.parse(x), image: JSON.parse(x).image, video: y ? JSON.parse(y).contentUrl : '' }; return g; } catch (e) { return { status: false, mess: "failed download" }; } }; const igdl = async (url) => { try { let result = { status: true, creator: "@kelvdra/scraper", media: [] } const { data } = await axios(`https://www.y2mate.com/mates/analyzeV2/ajax`, { method: "post", data: { k_query: url, k_page: "Instagram", hl: "id", q_auto: 0 }, headers: { "content-type": "application/x-www-form-urlencoded", "user-agent": "PostmanRuntime/7.32.2" } }) await data.links.video.map((video) => result.media.push(video.url)) return result } catch (err) { const result = { status: false, message: `Media not found` } return result } } const mfdl = async (url) => { try { const res = await fetch(`https://rianofc-bypass.hf.space/scrape?url=${encodeURIComponent(url)}`); const html = await res.json(); const $ = cheerio.load(html.html); const result = { filename: $('.dl-info').find('.intro .filename').text().trim(), type: $('.dl-btn-label').find('.filetype > span').text().trim(), size: $('.details li:contains("File size:") span').text().trim(), uploaded: $('.details li:contains("Uploaded:") span').text().trim(), ext: /\.(.*?)/.exec($('.dl-info').find('.filetype > span').eq(1).text())?.[1]?.trim() || 'bin', download: $('.input').attr('href') }; result.mimetype = lookup(result.ext.toLowerCase()) || 'application/octet-stream'; return { status: true, creator: '@kelvdra/scraper', result }; } catch (err) { return { status: false, message: err.message }; } } const fbPhoto = async (url) => { try { async function getNonce() { const { data: nonce } = await axios.get( 'https://thefdownloader.com/facebook-photo-downloader/', ) const _ = cheerio.load(nonce) const skripKontent = _('#hmd-facebook-downloader-js-extra').html() const match = /"nonce":"([a-zA-Z0-9]+)"/.exec(skripKontent) return match?.[1] } const nonce = await getNonce() const base = { url: { admin: 'https://thefdownloader.com/wp-admin/admin-ajax.php', }, } let data = new FormData() data.append('action', 'facebook_photo_action') data.append('facebook', `facebook_photo_url=${url}`) data.append('nonce', nonce) let response = await axios.post(base.url.admin, data, { headers: { ...data.getHeaders(), }, }) let $ = cheerio.load(response.data) let imageUrl = $('.facebook__media img').attr('src') return { status: true, creator: "@kelvdra/scraper", imageUrl } } catch (err) { return { status: false, message: err.message }; } } const fbVideo = async (u) => { try { if (!/^https?:\/\/(www\.)?facebook\.com/.test(u)) { throw new Error('Invalid Facebook URL'); } function generatePayload(y) { return { id: y, locale: 'id' }; } const pylox = generatePayload(u); const { data } = await axios.post('https://getmyfb.com/process', pylox); const $ = cheerio.load(data); const downloadLinks = []; const items = $('.results-list-item'); if (!items.length) { throw new Error('No download links found. The video might be private or unavailable.'); } items.each((_, el) => { const quality = $(el).text().trim(); const link = $(el).find('a').attr('href'); const filename = $(el).find('a').attr('download'); if (link) { downloadLinks.push({ quality, link, ...(filename && { filename }) }); } }); return { status: true, creator: "@kelvdra/scraper", downloadLinks } } catch (err) { return { status: false, message: err.message }; } }; const ttdl2 = async (url) => { try { const response = await axios.post( 'https://ssstik.io/abc?url=dl', new URLSearchParams({ id: url, locale: 'id', tt: 'QmZkaXFj' }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Mobile Safari/537.36', 'Referer': 'https://ssstik.io/id', 'HX-Request': 'true', 'HX-Trigger': '_gcaptcha_pt', 'HX-Target': 'target', 'HX-Current-URL': 'https://ssstik.io/id' } } ) const $ = cheerio.load(response.data) const isSlide = $('a.download_link.slide').length > 0 const result = { status: true, type: isSlide ? 'slide' : 'video', video: null, videoHD: null, audio: null, slides: [], slideAsVideo: null } result.audio = $('a.download_link.music').attr('href') || null if (isSlide) { $('a.download_link.slide').each((i, el) => { const image = $(el).prev('img').attr('data-splide-lazy') || null const download = $(el).attr('href') || null if (image && download) { result.slides.push({ image, download }) } }) result.slideAsVideo = $('a.slides_video').attr('href') || null } else { result.video = $('a.download_link.without_watermark').attr('href') || null result.videoHD = $('a.download_link.without_watermark_hd').attr('onclick') || null } return { status: true, creator: "@kelvdra/scraper", result } } catch (err) { return { status: false, message: err.message } } } module.exports = { ttdl, playmp3, playmp4, ytmp3, ytmp4, transcript, pindl, igdl, mfdl, fbPhoto, ttdl2, fbVideo };