/ src / app.ts
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();