app.ts
1 import * as fs from 'fs'; 2 import axios from 'axios'; 3 import { match } from 'assert'; 4 import {SerializeOptions, DeserializeOptions, parse, stringify} from 'hjson'; 5 import beautify from 'cssbeautify'; 6 import * as path from 'path'; 7 8 const BASE_URL: string = 'https://jackbox.tv/'; 9 const BUNDLE_URL: string = 'https://bundles.jackbox.tv/'; 10 11 async function fetchData(): Promise<void> { 12 try { 13 const response = await axios.get(BASE_URL); 14 const site: string = response.data; 15 16 const mainScriptMatch = site.match(/<script type="module" crossorigin src="(.*?\.js)"/); 17 const mainScript: string | null = mainScriptMatch ? mainScriptMatch[1].replace(/^\/+/, '') : null; 18 19 if (mainScript) { 20 console.log(mainScript); 21 22 if (!fs.existsSync('out')) { 23 fs.mkdirSync('out'); 24 } 25 26 const scriptResponse = await axios.get(`${BASE_URL}${mainScript}`); 27 const { webcrack } = await import('webcrack'); // Dynamic import 28 const scriptClean = await webcrack(scriptResponse.data); 29 fs.writeFileSync(`out/${mainScript}`, scriptClean.code, 'utf-8'); 30 fs.writeFileSync('out/index.html', site, 'utf-8'); 31 32 //TODO: cleanup the regex 33 const regex = /{\n.*main: {\n.*sha:.*,\n.*lastUpdated:.*,\n.*version:.*,\n.*type:.*,\n.*\n.*"@connect":[\s\S]*}\n.*}/g; 34 const matches = [...scriptClean.code.matchAll(regex)]; 35 let gameInfo = parse(matches[0][0]); 36 37 const fetchBundleData = async (key: string) => { 38 39 let js = gameInfo['main']['bundles'][key]['file']; 40 let css = gameInfo['main']['bundles'][key]['css'][0]; 41 let base = gameInfo['main']['bundles'][key]['base']; 42 43 console.log(`${BUNDLE_URL}${base}/${css}`); 44 45 const cssResp = await axios.get(`${BUNDLE_URL}${base}/${css}`); 46 47 48 49 // Create directory for CSS file if it doesn't exist 50 51 const cssDir = `out/bundles/${base}/assets`; 52 if (!fs.existsSync(cssDir)) { 53 fs.mkdirSync(cssDir, { recursive: true }); 54 } 55 const cssClean = beautify(cssResp.data); 56 fs.writeFileSync(`${cssDir}/${css.replace('assets/', '')}`, cssResp.data, 'utf-8'); 57 58 const jsResp = await axios.get(`${BUNDLE_URL}${base}/${js}`); 59 const jsClean = await webcrack(jsResp.data); 60 61 // Create directory for JS file if it doesn't exist 62 const jsDir = `out/bundles/${base}`; 63 if (!fs.existsSync(jsDir)) { 64 fs.mkdirSync(jsDir, { recursive: true }); 65 } 66 fs.writeFileSync(`${jsDir}/${js}`, jsClean.code, 'utf-8'); 67 let linkRegex = /(https:\/\/bundles\.jackbox\.tv\/main\/[^\/]+\/assets\/[a-z0-9]+\.(eot|png|jpg|gif|mp3|wav|js|css))/g; 68 let links = Array.from(jsClean.code.match(linkRegex) || []); 69 let links2 = Array.from(cssResp.data.match(linkRegex) || []); 70 71 async function downloadAndSaveFiles(urls: string[]): Promise<void> { 72 // Updated regex to match file extensions more accurately 73 const extensionRegex = /\.([a-zA-Z0-9]+)(?:[?#]|$)/; 74 75 for (const url of urls) { 76 const match = url.match(extensionRegex); 77 if (match) { 78 try { 79 const response = await axios.get(url, { responseType: 'arraybuffer' }); 80 81 // Extract the pathname and remove query parameters 82 const urlPath = new URL(url).pathname.split('?')[0]; 83 const localPath = path.join('out', 'bundles', urlPath); 84 85 // Create directories recursively 86 await fs.mkdirSync(path.dirname(localPath), { recursive: true }); 87 88 // Write file 89 await fs.writeFileSync(localPath, response.data); 90 } catch (error) { 91 console.error(`Error downloading ${url}:`, error); 92 } 93 } else { 94 } 95 } 96 } 97 await downloadAndSaveFiles(links as string[]); 98 await downloadAndSaveFiles(links2 as string[]); 99 }; 100 Object.keys(gameInfo['main']['bundles']).forEach(async (key: string) => { 101 await fetchBundleData(key); 102 }); 103 104 } 105 106 } catch (error) { 107 console.error('Error fetching data:', error); 108 } 109 } 110 111 fetchData();