|
// Get necessary data from the WIZ_global_data window object |
|
const sid = window.WIZ_global_data.FdrFJe |
|
const bl = window.WIZ_global_data.cfb2h |
|
const at = encodeURIComponent(window.WIZ_global_data.SNlM0e) |
|
|
|
// Get document language and generate random request ID |
|
const hl = document.documentElement.lang |
|
const reqid = Math.round((Math.random() * 100000) + 300000) |
|
|
|
// Define the RPC IDs for subscriptions and feed (endpoints for batchexecute calls) |
|
const rpcids = { |
|
"subscriptions": "Saeaed", |
|
"feed": "ncqJEe", |
|
} |
|
|
|
// Define the options for the fetch requests |
|
const fetchOptions = body => { |
|
return { |
|
"headers": { |
|
"accept": "*/*", |
|
"cache-control": "no-cache", |
|
"content-type": "application/x-www-form-urlencoded;charset=UTF-8", |
|
"pragma": "no-cache", |
|
"sec-fetch-dest": "empty", |
|
"sec-fetch-mode": "cors", |
|
"sec-fetch-site": "same-origin", |
|
"sec-gpc": "1", |
|
"x-same-domain": "1" |
|
}, |
|
"referrer": "https://podcasts.google.com/", |
|
"referrerPolicy": "origin", |
|
"body": body, |
|
"method": "POST", |
|
"mode": "cors", |
|
"credentials": "include" |
|
} |
|
} |
|
|
|
// Array to store all played episodes |
|
const allPlayedEpisodes = [] |
|
|
|
// Function to get all podcast IDs from the subscriptions |
|
async function getAllPodcastIds() { |
|
try { |
|
// Fetch data from the "subscriptions" endpoint |
|
const response = await fetch(`https://podcasts.google.com/_/PodcastsUi/data/batchexecute?rpcids=${rpcids.subscriptions}&source-path=%2Fsubscriptions&f.sid=${sid}&bl=${bl}&hl=${hl}&_reqid=${reqid}`, fetchOptions(`f.req=%5B%5B%5B%22${rpcids.subscriptions}%22%2C%22%5B%5D%22%2Cnull%2C%223%22%5D%5D%5D&at=${at}&`)); |
|
const data = await response.text(); |
|
|
|
// Parse the response data to get the individual podcast IDs |
|
const subscriptionsRaw = JSON.parse(JSON.parse(data.replace(")]}'", "").trim())[0][2])[0] |
|
const allPodcastIds = [] |
|
subscriptionsRaw.forEach((subscription) => { |
|
const podcastId = subscription[10] |
|
allPodcastIds.push(podcastId) |
|
}); |
|
|
|
// Return the array of podcast IDs |
|
return allPodcastIds |
|
} catch (err) { |
|
console.log(err); |
|
} |
|
} |
|
|
|
// Function to get played episodes for a given podcast ID |
|
async function getEpisodesPlayed(podcastId) { |
|
try { |
|
// Fetch data from the "feed" endpoint |
|
const response = await fetch(`https://podcasts.google.com/_/PodcastsUi/data/batchexecute?rpcids=${rpcids.feed}&source-path=%2Ffeed%2F${podcastId}&f.sid=${sid}&bl=${bl}&hl=${hl}&_reqid=${reqid + 1000}`, fetchOptions(`f.req=%5B%5B%5B%22${rpcids.feed}%22%2C%22%5B%5C%22${podcastId}%5C%22%5D%22%2Cnull%2C%221%22%5D%5D%5D&at=${at}&`)); |
|
const data = await response.text(); |
|
|
|
// Parse the response data to get the individual episodes |
|
const episodesRaw = JSON.parse(JSON.parse(data.replace(")]}'", "").trim())[0][2])[1][0]; |
|
const episodesPlayed = []; |
|
episodesRaw.forEach((episode) => { |
|
|
|
// Ignore unplayed episodes |
|
if (null != episode[3]) { |
|
const podcastName = episode[1]; |
|
const episodeName = episode[8]; |
|
|
|
// Generate row of data containing podcast name, date, episode name and progress |
|
const episodeData = [ |
|
podcastName.includes(',') ? `"${podcastName}"` : podcastName, |
|
new Date(episode[11] * 1000).toISOString().split('T')[0], |
|
episodeName.includes(',') ? `"${episodeName}"` : episodeName, |
|
episode[3] > 0.95 ? "✅" : Math.round(episode[3] * 100).toString() + "%" |
|
]; |
|
episodesPlayed.push(episodeData); |
|
} |
|
}); |
|
|
|
// Return the array of played episodes for the podcast |
|
return episodesPlayed; |
|
} catch (err) { |
|
console.log(err); |
|
} |
|
} |
|
|
|
// Function to log all played episodes and generate the CSV file |
|
async function logAllPlayedEpisodes() { |
|
// Get all podcast IDs |
|
const allMyIds = await getAllPodcastIds(); |
|
|
|
// Get played episodes for each podcast ID |
|
const allPlayedEpisodes = (await Promise.all(allMyIds.map(async podcastId => { |
|
const playedEpisodesInThisPodcast = await getEpisodesPlayed(podcastId); |
|
return playedEpisodesInThisPodcast; |
|
}))).flatMap(episodes => episodes); |
|
|
|
// Sort the played episodes: first ASC by podcast name and then DESC by date |
|
allPlayedEpisodes.sort((a, b) => { |
|
const podcastComparison = a[0].toLowerCase().localeCompare(b[0].toLowerCase()); |
|
if (podcastComparison !== 0) { |
|
return podcastComparison; |
|
} |
|
return b[1].localeCompare(a[1]); |
|
}); |
|
|
|
// Generate the CSV content |
|
let csvContent = "podcast,date,episode,played\n" |
|
+ allPlayedEpisodes.map(episode => episode.join(",")).join("\n"); |
|
|
|
// Download the CSV file |
|
const csvBlob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); |
|
const csvUrl = URL.createObjectURL(csvBlob); |
|
const link = document.createElement('a'); |
|
link.setAttribute('href', csvUrl); |
|
link.setAttribute('download', 'google_podcasts_listening_history.csv'); |
|
link.click(); |
|
} |
|
|
|
// Call the function to log all played episodes and generate the CSV file |
|
logAllPlayedEpisodes() |
Legend! This works a treat.
Had totally forgotton where I was, in a very linear podcast series and Google were no help in the transition away from a good app, even to their own bloody YT-Music App! *pain
Thanks again.