UNPKG

cloudapp-dl

Version:

CloudApp/Zight API client and CLI. Use as a CLI tool to download videos or as a programmatic library to interact with the Zight API.

240 lines (203 loc) 6.87 kB
import axios from 'axios'; import * as cheerio from 'cheerio'; import { loadConfig, updateConfig, clearSession, isSessionExpired } from './config.js'; const BASE_URL = 'https://share.zight.com'; const LOGIN_PAGE_URL = `${BASE_URL}/login`; const LOGIN_POST_URL = `${BASE_URL}/accounts/login`; /** * Default headers for requests */ const defaultHeaders = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'accept-language': 'en-US,en;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' }; /** * Extract session_id from Set-Cookie header * @param {Object} response - Axios response object * @returns {Object|null} - Session info with id and expiry */ export const extractSessionFromCookies = (response) => { const setCookieHeader = response.headers['set-cookie']; if (!setCookieHeader) return null; // Find the _session_id cookie const sessionCookie = setCookieHeader.find(cookie => cookie.startsWith('_session_id=')); if (!sessionCookie) return null; // Extract the session ID value const sessionIdMatch = sessionCookie.match(/_session_id=([^;]+)/); if (!sessionIdMatch) return null; const sessionId = sessionIdMatch[1]; // Extract expiry if present const expiresMatch = sessionCookie.match(/expires=([^;]+)/i); let sessionExpiry = null; if (expiresMatch) { sessionExpiry = new Date(expiresMatch[1]).toISOString(); } else { // If no expiry, set a default of 24 hours sessionExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); } return { sessionId, sessionExpiry }; }; /** * Update session from response cookies (for CLI usage - saves to config file) * @param {Object} response - Axios response object */ export const updateSessionFromResponse = (response) => { const sessionInfo = extractSessionFromCookies(response); if (sessionInfo) { updateConfig({ sessionId: sessionInfo.sessionId, sessionExpiry: sessionInfo.sessionExpiry }); return sessionInfo; } return null; }; /** * Fetch the login page and extract the authenticity token * @returns {Promise<{token: string, cookies: string}>} */ const fetchLoginPage = async () => { try { const response = await axios.get(LOGIN_PAGE_URL, { headers: defaultHeaders, maxRedirects: 5 }); const $ = cheerio.load(response.data); // Find the authenticity token - it's in a hidden input field const authenticityToken = $('input[name="authenticity_token"]').attr('value'); if (!authenticityToken) { throw new Error('Authenticity token not found'); } // Extract any cookies from the response for the subsequent POST const cookies = response.headers['set-cookie']; let cookieString = ''; if (cookies) { cookieString = cookies.map(cookie => cookie.split(';')[0]).join('; '); } return { token: authenticityToken, cookies: cookieString }; } catch (error) { if (error.response) { throw new Error(`Failed to fetch login page: ${error.response.status}`); } throw error; } }; /** * Perform login with email and password (programmatic - no file I/O, no console output) * Returns session info without saving to config file * @param {string} email - User email * @param {string} password - User password * @returns {Promise<Object>} - Login result with sessionId and sessionExpiry */ export const performLogin = async (email, password) => { try { // Step 1: Fetch login page and get authenticity token const { token, cookies } = await fetchLoginPage(); // Step 2: POST login credentials const loginData = new URLSearchParams({ utf8: '✓', authenticity_token: token, email: email, password: password }); const response = await axios.post(LOGIN_POST_URL, loginData.toString(), { headers: { ...defaultHeaders, 'content-type': 'application/x-www-form-urlencoded', 'origin': BASE_URL, 'referer': LOGIN_PAGE_URL, 'cookie': cookies }, maxRedirects: 0, validateStatus: (status) => status >= 200 && status < 400 }); // Extract session from response const sessionInfo = extractSessionFromCookies(response); if (!sessionInfo) { throw new Error('Login failed - no session cookie received'); } return { success: true, sessionId: sessionInfo.sessionId, sessionExpiry: sessionInfo.sessionExpiry }; } catch (error) { if (error.response && error.response.status === 401) { throw new Error('Invalid email or password'); } if (error.response && error.response.status === 422) { throw new Error('Invalid login request - check credentials'); } throw error; } }; /** * Perform login with email and password (CLI version - saves to config, logs to console) * @param {string} email - User email * @param {string} password - User password * @returns {Promise<Object>} - Login result with session info */ export const login = async (email, password) => { console.log('Fetching login page...'); console.log('Logging in...'); const result = await performLogin(email, password); // Save credentials and session to config updateConfig({ email, password, sessionId: result.sessionId, sessionExpiry: result.sessionExpiry }); return { success: true, message: 'Login successful', sessionExpiry: result.sessionExpiry }; }; /** * Re-login using stored credentials * @returns {Promise<Object>} - Login result */ export const relogin = async () => { const config = loadConfig(); if (!config.email || !config.password) { throw new Error('No stored credentials found. Please run "cloudapp-dl login" first.'); } console.log('Session expired, re-authenticating...'); return await login(config.email, config.password); }; /** * Ensure we have a valid session, re-login if needed * @returns {Promise<string>} - Valid session ID */ export const ensureValidSession = async () => { const config = loadConfig(); if (!config.email || !config.password) { throw new Error('Not logged in. Please run "cloudapp-dl login" first.'); } if (!config.sessionId || isSessionExpired()) { await relogin(); } // Reload config after potential re-login const updatedConfig = loadConfig(); return updatedConfig.sessionId; }; /** * Logout - clear session data */ export const logout = () => { clearSession(); console.log('Logged out successfully'); }; export default { login, performLogin, relogin, logout, ensureValidSession, updateSessionFromResponse, extractSessionFromCookies };