wolfyflutter

9xbuddy.site [youtube] [downloader] [video]

Jul 17th, 2025 (edited)
368
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.   base   : https://9xbuddy.site/
  3.   node   : v24.4.0
  4.   update : 23 Juli 2025
  5.   fungsi : buat download video youtube
  6.   note   : skrep nya aku set hanya untuk download video mp4 yang ada suaranya
  7.            yang di support sama situs nya. biar aku gk bingung. khusus yt aja :v
  8.            untuk return value skrep nya itu sebuah objek. kamu hanya fokus ke
  9.            filename dan buffer. iya di dalam objek nya ada buffer hasil download.
  10.            🗣️ : kenapa gk link download aja bang?
  11.            ya soal nya kalau link download terus lu pencet link nya ya bakalan
  12.            403, mungkin karena beda headers
  13. */
  14.  
  15. const xbuddy = async (urlOrKeyword, chosenFormat = "360p", customId="") => {
  16.     // common function
  17.     const validateString = (yourParam) => { if (typeof (yourParam) !== "string" || yourParam?.trim()?.length === 0) throw Error(`invalid param type. must be string and cannot be empty`) }
  18.     const sanitize = (string) => string.replace(/[^A-Za-z0-9 ]/g, '').replace(/ +/g, '_').toLowerCase()
  19.     const delay = async (ms) => new Promise(resolve => setTimeout(resolve, ms))
  20.     const reverseString = (string) => string.split("").reverse().join("")
  21.     const randomIdentifier = (customId) =>{
  22.         let id = customId?.toString()?.trim().length
  23.         return id ? customId : parseInt(Math.random().toString().substring(2)).toString(32)
  24.     }
  25.     const hit = async (description, url, options, outputType = "text") => {
  26.         try {
  27.             const response = await fetch(url, options)
  28.             if (!response.ok) throw Error(`${response.status} ${response.statusText}\n${await response.text()}`)
  29.             let result = null
  30.             try {
  31.                 if (outputType == "text") {
  32.                     result = await response.text()
  33.                 } else if (outputType == "json") {
  34.                     result = await response.json()
  35.                 } else if (outputType == "buffer") {
  36.                     const ab = await response.arrayBuffer()
  37.                     result = Buffer.from(ab)
  38.                 } else {
  39.                     throw Error(`invalid output type`)
  40.                 }
  41.                 return result
  42.  
  43.             } catch (awo) {
  44.                 throw Error(`gagal mengubah hasil fetch menjadi ${outputType}\n${awo.message}`)
  45.             }
  46.  
  47.         } catch (error) {
  48.             throw Error(`hit gagal. ${description}\n${error.message}`)
  49.         }
  50.     }
  51.  
  52.     // validate input
  53.     validateString(urlOrKeyword)
  54.     const allFormats = ["144p", "240p", "360p", "480p", "720p", "1080p"]
  55.     if (!allFormats.includes(chosenFormat)) throw Error(`invalid format. please pick one ${allFormats.join(", ")}`)
  56.  
  57.     // konstan
  58.     const tools = {
  59.         decode64(e) {
  60.             if (e = e.replace(/\s/g, ""),
  61.                 /^[a-z0-9\+\/\s]+\={0,2}$/i.test(e) && !(e.length % 4 > 0)) {
  62.                 var t, r, n = 0, s = [];
  63.                 for (e = e.replace(/=/g, ""); n < e.length;) {
  64.                     switch (t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(e.charAt(n)),
  65.                     n % 4) {
  66.                         case 1:
  67.                             s.push(String.fromCharCode(r << 2 | t >> 4));
  68.                             break;
  69.                         case 2:
  70.                             s.push(String.fromCharCode((15 & r) << 4 | t >> 2));
  71.                             break;
  72.                         case 3:
  73.                             s.push(String.fromCharCode((3 & r) << 6 | t))
  74.                     }
  75.                     r = t,
  76.                         n++
  77.                 }
  78.                 return s.join("")
  79.             }
  80.         },
  81.         ord(e) {
  82.             var t = "".concat(e)
  83.                 , r = t.charCodeAt(0);
  84.             if (r >= 55296 && r <= 56319) {
  85.                 var n = r;
  86.                 return 1 === t.length ? r : 1024 * (n - 55296) + (t.charCodeAt(1) - 56320) + 65536
  87.             }
  88.             return r
  89.         },
  90.         encode64(e) {
  91.             if (/([^\u0000-\u00ff])/.test(e))
  92.                 throw new Error("Can't base64 encode non-ASCII characters.");
  93.             for (var t, r, n, s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", a = 0, o = []; a < e.length;) {
  94.                 switch (t = e.charCodeAt(a),
  95.                 n = a % 3) {
  96.                     case 0:
  97.                         o.push(s.charAt(t >> 2));
  98.                         break;
  99.                     case 1:
  100.                         o.push(s.charAt((3 & r) << 4 | t >> 4));
  101.                         break;
  102.                     case 2:
  103.                         o.push(s.charAt((15 & r) << 2 | t >> 6)),
  104.                             o.push(s.charAt(63 & t))
  105.                 }
  106.                 r = t,
  107.                     a++
  108.             }
  109.             return 0 == n ? (o.push(s.charAt((3 & r) << 4)),
  110.                 o.push("==")) : 1 == n && (o.push(s.charAt((15 & r) << 2)),
  111.                     o.push("=")),
  112.                 o.join("")
  113.         },
  114.         encrypt(e, t) {
  115.             for (var r = "", n = 0; n < e.length; n++) {
  116.                 var s = e.substr(n, 1)
  117.                     , a = t.substr(n % t.length - 1, 1);
  118.                 s = Math.floor(this.ord(s) + this.ord(a)),
  119.                     r += s = String.fromCharCode(s)
  120.             }
  121.             return this.encode64(r)
  122.         },
  123.         decrypt(e, t) {
  124.             var r = "";
  125.             e = this.decode64(e);
  126.             for (var n = 0; n < e.length; n++) {
  127.                 var s = e.substr(n, 1)
  128.                     , a = t.substr(n % t.length - 1, 1);
  129.                 s = Math.floor(this.ord(s) - this.ord(a)),
  130.                     r += s = String.fromCharCode(s)
  131.             }
  132.             return r
  133.         },
  134.         hex2bin(e) {
  135.             var t, r = [], n = 0;
  136.             for (t = (e += "").length; n < t; n += 2) {
  137.                 var s = parseInt(e.substr(n, 1), 16)
  138.                     , a = parseInt(e.substr(n + 1, 1), 16);
  139.                 if (isNaN(s) || isNaN(a))
  140.                     return !1;
  141.                 r.push(s << 4 | a)
  142.             }
  143.             return String.fromCharCode.apply(String, r)
  144.         },
  145.  
  146.     }
  147.  
  148.  
  149.     const myHeaders = {
  150.         "accept": "application/json, text/plain, */*",
  151.         "accept-encoding": "gzip, deflate, br, zstd",
  152.         "accept-language": "en-GB,en;q=0.9,en-US;q=0.8",
  153.         "cache-control": "no-cache",
  154.         "content-type": "application/json; charset=UTF-8",
  155.         "pragma": "no-cache",
  156.         "priority": "u=1, i",
  157.         "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
  158.         "sec-ch-ua-mobile": "?0",
  159.         "sec-ch-ua-platform": "\"Windows\"",
  160.         "sec-fetch-dest": "empty",
  161.         "sec-fetch-mode": "cors",
  162.         "sec-fetch-site": "cross-site",
  163.         "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
  164.         "x-requested-domain": "9xbuddy.site",
  165.         "x-requested-with": "xmlhttprequest",
  166.         "origin": "https://9xbuddy.site",
  167.         "referer": "https://9xbuddy.site/",
  168.     }
  169.  
  170.     // another function
  171.     const getAuthToken = async (identifier) => {
  172.         const taskName = `${identifier}-getAuthToken`
  173.         console.time(taskName)
  174.         const headers = {
  175.             "accept": "application/json, text/plain, */*",
  176.             "accept-encoding": "gzip, deflate, br, zstd",
  177.             "accept-language": "en-GB,en;q=0.9,en-US;q=0.8",
  178.             "cache-control": "no-cache",
  179.             "content-type": "application/json; charset=UTF-8",
  180.             "pragma": "no-cache",
  181.             "priority": "u=1, i",
  182.             "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"",
  183.             "sec-ch-ua-mobile": "?0",
  184.             "sec-ch-ua-platform": "\"Windows\"",
  185.             "sec-fetch-dest": "empty",
  186.             "sec-fetch-mode": "cors",
  187.             "sec-fetch-site": "cross-site",
  188.             "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
  189.         }
  190.         const url = "https://9xbuddy.site/"
  191.         const html = await hit(`homepage`, url, { headers })
  192.  
  193.         const cssVersion = html.match(/main\.(.+?)\.css/)?.[1]
  194.         if (!cssVersion) throw Error(`tidak bisa menemukan match match css version`)
  195.  
  196.         let json = html.match(/window.__INIT__ = (.+?)<\/script>/)?.[1]
  197.         if (!json) throw Error(`tidak bisa menamukan match json`)
  198.  
  199.         if (json) json = JSON.parse(json)
  200.         const ua = json?.ua
  201.         if (!ua) throw Error(`gagal menemukan value ua`)
  202.  
  203.         const appVersion = json?.appVersion
  204.         if (!appVersion) throw Error(`gagal menemukan value appVersion`)
  205.  
  206.         const hostname = new URL(url).hostname
  207.  
  208.         // mulai generate
  209.  
  210.         const satu = hostname //9xbuddy.site/
  211.         const dua = reverseString(cssVersion) // ea6534dae7db17314ea0
  212.         const tiga = reverseString(ua).substring(0, 10) // ,AjLw4CMug
  213.         const empat = "SORRY_MATE_IM_NOT_GONNA_TELL_YOU"
  214.         const lima = "xbuddy123sudo-" + appVersion // xbuddy123sudo-11.1.5
  215.         const enam = appVersion // 11.1.5
  216.         const payload = satu + dua + tiga + empat + lima + enam
  217.         const authToken = tools.encrypt(payload, dua)
  218.  
  219.         const result = {
  220.             cssVersion,
  221.             ua,
  222.             appVersion,
  223.             hostname,
  224.             authToken,
  225.         }
  226.         console.timeEnd(taskName)
  227.         return result
  228.     }
  229.  
  230.     const getAccessToken = async (authToken, identifier) => {
  231.         const taskName = `${identifier}-getAccessToken`
  232.         console.time(taskName)
  233.         const headers = {
  234.             "x-access-token": "false",
  235.             "x-auth-token": authToken,
  236.             ...myHeaders
  237.         }
  238.         const url = "https://ab1.9xbud.com/token"
  239.         const json = await hit(`access token`, url, { headers, "method": "post" }, "json")
  240.         const accessToken = json?.access_token
  241.         if (!accessToken) throw Error(`access token kosong`)
  242.         console.timeEnd(taskName)
  243.         return { accessToken }
  244.     }
  245.  
  246.     const generateSig = (keywordOrUrl, authToken) => {
  247.         // create sig
  248.         const plain = encodeURIComponent(keywordOrUrl)
  249.         const key = authToken + "jv7g2_DAMNN_DUDE"
  250.         const _sig = tools.encrypt(plain, key)
  251.         return _sig
  252.     }
  253.  
  254.     const extract = async (keywordOrUrl, authToken, accessToken, identifier) => {
  255.         const taskName = `${identifier}-extract`
  256.         console.time(taskName)
  257.         const headers = {
  258.             "x-access-token": accessToken,
  259.             "x-auth-token": authToken,
  260.             ...myHeaders
  261.         }
  262.  
  263.         const body = JSON.stringify({
  264.             _sig: generateSig(keywordOrUrl, authToken),
  265.             url: encodeURIComponent(keywordOrUrl),
  266.             searchEngine: "yt"
  267.         })
  268.  
  269.         const url = "https://ab.9xbud.com/extract"
  270.  
  271.         const result = await hit(`extract`, url, { headers, body, "method": "post" }, "json")
  272.         console.timeEnd(taskName)
  273.         return result
  274.     }
  275.  
  276.     const handleFormat = (extractObject, chosenFormat, hostname, cssVersion, identifier) => {
  277.         const filtered = extractObject?.response?.formats?.filter(v => allFormats.includes(v.quality))
  278.         if (!filtered) throw Error (`aneh.. tidak ada format apapun untuk di download`)
  279.         const tokenUrl = extractObject.response.token
  280.  
  281.         let find
  282.         for (let i = allFormats.indexOf(chosenFormat); i > -1; i--) {
  283.             find = filtered.find(v => v.quality == allFormats[i])
  284.             if (find) break
  285.         }
  286.  
  287.         if (!find) throw Error(`format ada tapi gak ada yang audio+video combined.\nsilakan cek sendiri di https://${hostname}/process?url=${extractObject?.response?.request?.url || ""}`)
  288.         if (allFormats.indexOf(chosenFormat) > allFormats.indexOf(find.quality)) console.log(`${identifier}-${chosenFormat} gak adaaaa. auto fallback ke yang ada ada aja. ${find.quality} ya.. hehe`)
  289.  
  290.         const url = find.url
  291.         const cip = reverseString(tools.hex2bin(url))
  292.         const key = `SORRY_MATE${hostname.length}${cssVersion}${tokenUrl}`
  293.         find.url = tools.decrypt(cip, key)
  294.         return find
  295.     }
  296.  
  297.     const convert = async (selectedFormatObject, authToken, accessToken, identifier) => {
  298.         const taskName = `${identifier}-convert`
  299.         console.time(taskName)
  300.         const headers = {
  301.             "x-requested-domain": "9xbuddy.site",
  302.             "x-requested-with": "xmlhttprequest",
  303.             "origin": "https://9xbuddy.site",
  304.             "referer": "https://9xbuddy.site/",
  305.             "content-type": "application/json; charset=UTF-8",
  306.             "x-access-token": accessToken,
  307.             "x-auth-token": authToken,
  308.             ...myHeaders
  309.         }
  310.         const uid = selectedFormatObject.url.split("/")[2]
  311.         const url = selectedFormatObject.url.split("/")[3]
  312.         const body = JSON.stringify({ uid, url })
  313.         const api = "https://ab1.9xbud.com/convert"
  314.         const result = await hit(taskName, api, { headers, body, "method": "post" }, "json")
  315.         console.timeEnd(taskName)
  316.         return result
  317.     }
  318.     const progress = async (convertObject, authToken, accessToken, identifier) => {
  319.         const taskName = `${identifier}-progress`
  320.         //console.time(taskName)
  321.         const headers = {
  322.  
  323.             "x-access-token": accessToken,
  324.             "x-auth-token": authToken,
  325.             ...myHeaders
  326.         }
  327.         const uid = convertObject.url.split("/")[2]
  328.         const body = JSON.stringify({ uid })
  329.         const result = await hit(taskName, `https://ab1.9xbud.com/progress`, { headers, body, "method": "post" }, "json")
  330.         //console.timeEnd(taskName)
  331.         return result
  332.     }
  333.     const downloaderSakti = async (progressObject, authToken, accessToken, identifier) => {
  334.         const taskName = `${identifier}-download`
  335.         console.time(taskName)
  336.         const headers = {
  337.             "x-access-token": accessToken,
  338.             "x-auth-token": authToken,
  339.             ...myHeaders
  340.         }
  341.         const url = progressObject.response.url
  342.         const buffer = await hit(taskName, url, { headers }, "buffer")
  343.         console.timeEnd(taskName)
  344.         return buffer
  345.     }
  346.  
  347.     // main function
  348.     const run = async (urlOrKeyword, chosenFormat, customId) => {
  349.  
  350.         let temp = {}
  351.  
  352.         // save custom identifier ke objek temp
  353.         temp.identifier = randomIdentifier(customId)
  354.         console.log(`[NEW TASK] ${temp.identifier}`)
  355.  
  356.         // mendapatkan value cssVersion, ua, appVersion, hostname, authToken dan save di temp object
  357.         temp = { ...temp, ...await getAuthToken(temp.identifier) }
  358.  
  359.         // mendapatkan value accessToken, save di object temp object
  360.         temp = { ...temp, ...await getAccessToken(temp.authToken, temp.identifier) }
  361.  
  362.         // ekstrak url, mendapatkan value dari server
  363.         let extractObject = await extract(urlOrKeyword, temp.authToken, temp.accessToken, temp.identifier)
  364.         const responseType = extractObject?.response?.type
  365.         if (!responseType) throw Error(`tidak menemukan value response.type. anomali json return\n${JSON.stringify(extractObject, null, 2)}`)
  366.  
  367.         // kalau tipe nya search maka kita ambil response sever yang url video lalu feed url itu ke api extract
  368.         if (responseType === "search") {
  369.             const firstYoutubeUrl = extractObject.response.formats[0].pageAddress.trim()
  370.             console.log(`${temp.identifier}-mencari url untuk ${urlOrKeyword}. dont worry its a fake delay 5 detik lol :v`)
  371.             await delay(5000)
  372.             console.log(`${temp.identifier}-pick ${firstYoutubeUrl}`)
  373.             extractObject = await extract(firstYoutubeUrl, temp.authToken, temp.accessToken, temp.identifier)
  374.         }
  375.  
  376.         // save some metadata
  377.         temp.title = extractObject?.response?.title || `(no title)`
  378.         temp.uploader = extractObject?.response?.uploader || `(no uploader)`
  379.  
  380.         // pick "best" selected format, jika vid hanya 240p dan kamu mau 1080p. maka akan fallback ke best format yaitu 240p
  381.         const selectedFormatObject = handleFormat(extractObject, chosenFormat, temp.hostname, temp.cssVersion, temp.identifier)
  382.  
  383.         // simpan quality name ke temp object
  384.         temp.quality = selectedFormatObject.quality
  385.  
  386.         // buat string filename
  387.         const { title, uploader } = extractObject.response
  388.         temp.fileName = sanitize(`${title} by ${uploader} ${selectedFormatObject.quality}`) + `.mp4`
  389.  
  390.         // convert, gua kagak tau jir ini value dari api nya kagak di apa apain wkkw
  391.         const convertObject = await convert(selectedFormatObject, temp.authToken, temp.accessToken, temp.identifier)
  392.  
  393.         // cek progress dengan while luv <3
  394.         const MAX_FETCH_ATTEMPT = 100
  395.         let apiHit = 0
  396.         let progressObject = {}
  397.  
  398.         console.time(`${temp.identifier}-cek progress`)
  399.         do {
  400.             apiHit++
  401.             if (apiHit > MAX_FETCH_ATTEMPT) throw Error(`mencapai limit hit api ${MAX_FETCH_ATTEMPT} kali`)
  402.             progressObject = await progress(selectedFormatObject, temp.authToken, temp.accessToken, temp.identifier)
  403.             console.log(`${temp.identifier}-cek progress #${apiHit}`)
  404.             if (progressObject?.status === 0) throw Error(`gagal mendownload video, kata situs nya : You seem to be offline or the request got blocked,\nini json lengkap nyah\n${JSON.stringify(progressObject, null, 2)}`)
  405.             if (!progressObject?.response) await new Promise(re => setTimeout(re, 5000))
  406.         } while (!progressObject.response)
  407.         console.timeEnd(`${temp.identifier}-cek progress`)
  408.  
  409.  
  410.         // simpan download url ke temp object
  411.         temp.downloadUrl = progressObject.response.url
  412.  
  413.         // simpan ukuran video untuk kebutuhan metadata mendatang
  414.         temp.videoSize = progressObject.response.size
  415.  
  416.         // download dan simpan buffer nya ke temp object
  417.         console.log(`${temp.identifier}-sabar ya aku sedang mendownload videonya untuk mu... ${temp.videoSize}`)
  418.         const buffer = await downloaderSakti(progressObject, temp.authToken, temp.accessToken, temp.identifier,)
  419.         temp.buffer = buffer
  420.  
  421.         const wolep = {
  422.             title : temp.title,
  423.             uploader : temp.uploader,
  424.             quality : temp.quality,
  425.             fileName: temp.fileName,
  426.             videoSize: temp.videoSize,
  427.             buffer: temp.buffer
  428.         }
  429.        
  430.         return wolep
  431.     }
  432.     return await run(urlOrKeyword, chosenFormat, customId)
  433. }
  434.  
  435. // cara pakai
  436. xbuddy("me at the zoo","1080p")
  437. .then (data => {
  438.     console.log(data)
  439.  
  440.     // handle pakai logika sendiri ya hehe
  441.     const fs = require ("fs")
  442.     fs.writeFileSync(`./${data.fileName}`, data.buffer)
  443.     console.log(`file berhasil di simpan dengan nama ${data.fileName}`)})
  444. .catch(err => {
  445.     console.log(`task failed.\n${err.message}`)
  446. })
  447.  
  448.  
  449. /* output
  450. {
  451.   title: 'Me at the zoo',
  452.   uploader: 'jawed',
  453.   quality: '240p',
  454.   fileName: 'me_at_the_zoo_by_jawed_240p.mp4',
  455.   videoSize: '726.78 KB',
  456.   buffer: <Buffer 00 00 00 20 66 74 79 70 69 73 6f 6d 00 00 02 00 69 73 6f 6d 69 73 6f 32 61 76 63 31 6d 70 34 31 00 00 00 08 66 72 65 65 00 0b 31 3e 6d 64 61 74 00 00 ... 744169 more bytes>
  457. }
  458. */
Add Comment
Please, Sign In to add comment