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
JavaScript
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
};