import express from 'express'; import { google } from 'googleapis'; import open from 'open'; import fs from 'fs'; import path from 'path'; import { tokenManager } from './tokenManager.js'; (await import('dotenv')).config({ path: './Downloader/secret/config.env', debug: true }); const app = express(); const port = 3000; const { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } = process.env, manager = new tokenManager({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, redirectUri: REDIRECT_URI, tokenPath: 'Downloader/secret/token.json' }); const oauth2Client = manager.getAuthClient(); // scope to read playlist items/liked videos const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; let downloadStatus = 'idle'; // can be: 'idle', 'in-progress', 'completed', 'error' //#region oauth flow app.get('/auth', async (_req, res) => { const t = manager.loadToken(); if (t) return res.redirect('/choose-playlist'); // generate auth url const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES }); // automatically open the url in the default browser const c = await open(authUrl).catch((err) => { console.error('error opening browser:', err); return res.status(500).send('failed to open browser for oauth.'); }); c.on('close', () => res.redirect('/choose-playlist')) }); app.get('/oauth2callback', async (req, res) => { try { const code = req.query.code; const { tokens } = await oauth2Client.getToken(code); oauth2Client.setCredentials(tokens); manager.saveToken(tokens); // close the window res.sendStatus(200); } catch (err) { console.error('error retrieving token:', err); res.status(500).send('error retrieving token.'); } }); //#endregion //#region youtube stuffs async function getAllPlaylists(auth) { const youtube = google.youtube('v3'); let playlists = []; let nextPageToken = null; do { const response = await youtube.playlists.list({ auth, part: 'snippet', mine: true, maxResults: 50, pageToken: nextPageToken }); if (response.data.items) { playlists = playlists.concat(response.data.items); } nextPageToken = response.data.nextPageToken; } while (nextPageToken); return playlists; } async function getPlaylistItems(playlistId, auth) { const youtube = google.youtube('v3'); let items = []; let nextPageToken = null; do { const response = await youtube.playlistItems.list({ auth, part: 'snippet,contentDetails', playlistId, maxResults: 50, pageToken: nextPageToken }); if (response.data.items) { items = items.concat(response.data.items); } nextPageToken = response.data.nextPageToken; } while (nextPageToken); return items.map(o => `https://music.youtube.com/watch?v=${o.id}`); } //#endregion //#region routes app.get('/choose-playlist', async (_req, res) => { try { if (!oauth2Client.credentials || !oauth2Client.credentials.access_token) { const t = manager.loadToken(); if (!t) return res.redirect('/auth'); } const playlists = await getAllPlaylists(oauth2Client); let html = ` choose playlist

choose a playlist to download


`; res.send(html); } catch (err) { console.error('error fetching playlists:', err); res.status(500).send('error fetching playlists.'); } }); /** * called when the user has selected a playlist from the popup * fetch all items, write them to a json file, and update the status */ app.get('/download-playlist', async (req, res) => { try { if (!oauth2Client.credentials || !oauth2Client.credentials.access_token) { return res .status(401) .send('error: oauth2 client not authorized. go to /auth first.'); } const { playlistId } = req.query; if (!playlistId) { return res .status(400) .send('missing playlist id. please choose a playlist.'); } // set status to in-progress downloadStatus = 'in-progress'; // fetch the playlist items const items = await getPlaylistItems(playlistId, oauth2Client); // create a data folder if it doesn't exist const dataDir = path.join(process.cwd(), 'data'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir); } const outFile = path.join(dataDir, `playlist_${playlistId}.json`); fs.writeFileSync(outFile, JSON.stringify(items, null, 2), 'utf8'); downloadStatus = 'completed'; res.send(` download complete

download complete!

downloaded ${items.length} items to ${outFile}

check status

`); } catch (err) { console.error('error downloading playlist:', err); downloadStatus = 'error'; res.status(500).send('error downloading playlist.'); } }); app.get('/status', (_req, res) => { let html = ` download status

current status: ${downloadStatus}

`; res.send(html); }); //#endregion app.listen(port, () => { console.log(`server listening on http://localhost:${port}`); console.log(`go to http://localhost:${port}/auth to start oauth flow`); });