// ==UserScript==
// @name Netflix Episode title and description Extractor
// @namespace https://greasyfork.org/en/scripts/521575-netflix-episode-title-and-description-extractor
// @version 3.1
// @description Extract episode number, title, and description from Netflix and save to file, with cookie handling
// @author Abu3safeer
// @match https://www.netflix.com/*/title/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_cookie
// @connect web.prod.cloud.netflix.com
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Create and style the button
const button = document.createElement('button');
button.innerText = 'Fetch Show Info';
button.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
padding: 10px 20px;
background-color: #e50914;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: Netflix Sans,Helvetica Neue,Segoe UI,Roboto,Ubuntu,sans-serif;
`;
// Add hover effect
button.addEventListener('mouseover', () => {
button.style.backgroundColor = '#f40612';
});
button.addEventListener('mouseout', () => {
button.style.backgroundColor = '#e50914';
});
// Function to extract show ID from URL
function getShowIdFromUrl() {
const match = window.location.pathname.match(/\/title\/(\d+)/);
return match ? match[1] : null;
}
async function fetchShowInfo(showId) {
console.log('Starting fetch for show ID:', showId);
// Get locale from Netflix cookies
let locale = 'en-US'; // default fallback
await new Promise((resolve) => {
GM_cookie.list({ domain: '.netflix.com' }, (cookies) => {
const localeCookie = cookies.find(c => c.name === 'locale');
if (localeCookie) {
locale = localeCookie.value;
console.log('Found Netflix locale:', locale);
}
resolve();
});
});
const headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": "https://www.netflix.com",
"Referer": "https://www.netflix.com/",
"Accept-Language": locale,
"x-netflix-user-locale": locale
};
// Update season payload with locale
const payloadSeasons = {
"operationName": "PreviewModalEpisodeSelector",
"variables": {
"seasonCount": 100,
"showId": showId,
"locale": locale
},
"extensions": {
"persistedQuery": {
"id": "b1213a1e-19d0-42e6-aeed-7a29d855346c",
"version": 102
}
}
};
// Update the episode payload creation to include locale
const createEpisodePayload = (seasonId) => ({
"operationName": "PreviewModalEpisodeSelectorSeasonEpisodes",
"variables": {
"seasonId": seasonId,
"count": 100,
"opaqueImageFormat": "JPG",
"artworkContext": {},
"locale": locale
},
"extensions": {
"persistedQuery": {
"id": "314df063-5a11-4b60-87d5-765ea6e3fc91",
"version": 102
}
}
});
// Helper function for downloading JSON files
function downloadJSON(filename, data) {
console.log(`Preparing to download ${filename}`, data);
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
GM_download({
url: url,
name: filename,
onload: () => {
console.log(`Successfully downloaded ${filename}`);
URL.revokeObjectURL(url);
},
onerror: (error) => {
console.error(`Failed to download ${filename}:`, error);
}
});
}
// Helper function to make GM_xmlhttpRequest return a Promise
function makeRequest(url, payload) {
console.log('Making request to:', url);
console.log('With payload:', payload);
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: url,
headers: headers,
data: JSON.stringify(payload),
withCredentials: true,
anonymous: false,
responseType: 'json',
onload: function(response) {
console.log('Received response:', response);
try {
const data = JSON.parse(response.responseText);
console.log('Parsed response data:', data);
if (response.status === 200) {
resolve(data);
} else {
console.error('Request failed with status:', response.status);
reject(new Error(`HTTP ${response.status}: ${data.message || 'Unknown error'}`));
}
} catch (e) {
console.error('Failed to parse response:', e);
console.log('Raw response:', response.responseText);
reject(new Error('Failed to parse response'));
}
},
onerror: function(error) {
console.error('Request failed:', error);
reject(new Error('Network error occurred'));
},
ontimeout: function() {
console.error('Request timed out');
reject(new Error('Request timed out'));
}
});
});
}
try {
// First request to get show details and seasons
console.log('Fetching seasons data...');
const data = await makeRequest("https://web.prod.cloud.netflix.com/graphql", payloadSeasons);
console.log('Received seasons data:', data);
if (!data.data?.videos?.[0]) {
console.error('No video data found in response');
throw new Error("Could not fetch show data");
}
const videoData = data.data.videos[0];
const showData = {
show_id: showId,
seasons: []
};
if (!videoData.seasons?.edges) {
throw new Error("No seasons found");
}
// Process each season
for (const season of videoData.seasons.edges) {
console.log('Processing season:', season);
if (!season.node) continue;
const seasonNode = season.node;
const seasonInfo = {
season_name: seasonNode.title || "Unknown Season",
episodes: []
};
// Get episodes for each season
const payloadEpisodes = createEpisodePayload(seasonNode.videoId);
console.log('Fetching episodes for season:', seasonInfo.season_name);
const episodeData = await makeRequest("https://web.prod.cloud.netflix.com/graphql", payloadEpisodes);
console.log('Received episode data:', episodeData);
// Update the episodes path
const episodes = episodeData.data?.videos?.[0]?.episodes?.edges || [];
console.log('Found episodes:', episodes.length);
for (const episode of episodes) {
if (!episode.node) continue;
console.log('Processing episode:', episode.node);
const episodeNode = episode.node;
const episodeInfo = {
episode_name: episodeNode.title || "Unknown Title",
episode_number: episodeNode.number || 0,
episode_description: episodeNode.contextualSynopsis?.text || "No description available"
};
console.log('Created episode info:', episodeInfo);
seasonInfo.episodes.push(episodeInfo);
}
showData.seasons.push(seasonInfo);
console.log('Creating simplified season data');
// Create simplified season data
const simpleEpisodes = seasonInfo.episodes.map(ep => ({
episodeNumber: String(ep.episode_number),
episodeTitle: ep.episode_name,
description: ep.episode_description
}));
console.log('Simplified episodes:', simpleEpisodes);
const seasonFilename = `${showId}_${seasonInfo.season_name.replace(/ /g, '_')}.json`;
console.log(`Saving season data to ${seasonFilename}`);
downloadJSON(seasonFilename, simpleEpisodes);
}
console.log('Saving complete show data');
downloadJSON(`netflix_show_${showId}.json`, showData);
return true;
} catch (error) {
console.error('Error in fetchShowInfo:', error);
alert(`Error fetching show data: ${error.message}`);
return false;
}
}
// Click handler
button.addEventListener('click', async () => {
const showId = getShowIdFromUrl();
if (!showId) {
alert('Could not find show ID in URL');
return;
}
button.disabled = true;
button.innerText = 'Fetching...';
const success = await fetchShowInfo(showId);
button.disabled = false;
button.innerText = 'Fetch Show Info';
if (success) {
alert('Show information has been saved!');
}
});
// Add button to page
document.body.appendChild(button);
})();