I'm running a script on my personal Google account using Spotify API. I'm limited to six minutes for each execution run time. I need the execution to run longer than that for me to retrieve all the episodes for my list of podcasts.
Is there a way for me to automatically run the execution again once I reach the six-minute time-out and pick up where the previous execution had left off? I don't want to manually edit the GSheets range in the script where I read in the list of podcasts for every execution. I would also like the clear()
code in my script to only run in the first round of execution - I don't want to clear all of the episodes from the first run when the consecutive executions follow.
function getEpisodes() {
var clientId = <your Spotify API client ID>;
var clientSecret = <your Spotify API client secret>;
var ss = SpreadsheetApp.openById(<sheet ID>);
var sListOfPodcasts = ss.getSheetByName("List of podcasts");
var sOutput = ss.getSheetByName("Output");
var arrPodcasts = sListOfPodcasts.getRange("A2:A").getValues();
sOutput.getRange("A2:L").clear();
var url = "https://accounts.spotify.com/api/token";
var params = {
method: "post",
headers: {"Authorization" : "Basic " + Utilities.base64Encode(clientId + ":" + clientSecret)},
payload: {grant_type: "client_credentials"},
};
var res = UrlFetchApp.fetch(url, params);
var obj = JSON.parse(res.getContentText());
var token = obj.access_token;
Logger.log("token = " + token);
var parameters = {
method: "GET",
headers: {
"Authorization" : "Bearer " + token
},
json : true,
};
for (const show of arrPodcasts) {
let offset = 0;
let j = 1; // this is later used to index the episodes per podcast in the logs
var getPodcast = "https://api.spotify.com/v1/shows/" + show + "/episodes";
var fetchPodcast = UrlFetchApp.fetch(getPodcast, parameters);
var totEps = JSON.parse(fetchPodcast.getContentText()).total
Logger.log("total episodes = " + totEps);
let n = Math.floor(totEps/50) + 1; // determine number of loops needed to retrieve all episodes
Logger.log("We need to loop " + n + " times");
for (c = 0; c < n; c++) {
var podcasts = "https://api.spotify.com/v1/shows/" + show + "/episodes?offset=" + offset + "&limit=50&market=GB";
Logger.log(podcasts);
Logger.log("Offset = " + offset);
var nameShow = JSON.parse(UrlFetchApp.fetch("https://api.spotify.com/v1/shows/" + show + "/?market=gb", parameters).getContentText()).name;
var publisher = JSON.parse(UrlFetchApp.fetch("https://api.spotify.com/v1/shows/" + show + "/?market=gb", parameters).getContentText()).publisher;
Logger.log(nameShow);
try {
var podcast = UrlFetchApp.fetch(podcasts, parameters);
}
catch(err) {
Logger.log("Move onto the next podcast");
}
var object = JSON.parse(podcast.getContentText());
offset = (c+1) * 50;
Logger.log("Offset = " + offset);
if (c == n) {
break; // break the loop when we retrive the last batch of episodes, then move onto the next podcast
}
for (let b = 0; b < 1; b++) {
for (const episode of object.items) {
Logger.log(j + ') ' + episode.name + '\n'+ episode.release_date);
j = j + 1; // index the episodes for each podcast
var rowStart = sOutput.getLastRow() + 1;
sOutput.getRange(rowStart, 1, 1, 10).setValues([[nameShow, publisher, episode.name, episode.release_date, episode.description, episode.type, episode.explicit, episode.duration_ms, "https://open.spotify.com/embed/episode/" + episode.id, episode.images[0].url]]);
}
}
}
}
}
Below is what my list of podcast episodes looks like on GSheets.
Many thanks in advance!
I received great help from u/JetCarson on Reddit and got a solution.
Set up a time-based trigger for spotify()
. Don't worry the ScriptApp.deleteTrigger(triggers[i])
part of the script will not delete the spotify()
trigger.
function spotify() {
clearProperties();
getEpisodes();
}
// run this function first to clear the properties before reading in values from the Spotify API
function clearProperties() {
PropertiesService.getScriptProperties().deleteProperty('LastShowIndexRead');
PropertiesService.getScriptProperties().deleteProperty('lastEpisodeOffset');
};
function getEpisodes() {
var ss = SpreadsheetApp.openById(<sheetID>);
var sListOfPodcasts = ss.getSheetByName("List of podcasts");
var sOutput = ss.getSheetByName("Output");
var arrPodcasts = sListOfPodcasts.getRange("A2:A").getValues();
//these are needed to ensure we don't go over 360 seconds of execution time
const MAX_TIME = 5 * 60 * 1000; // run the execution for 5 mins
var startTime = new Date();
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() == 'getEpisodes') { // make sure the name of the function in if statement is in string format
ScriptApp.deleteTrigger(triggers[i]);
}
}
//this next variable will be used to determine where we start off on this call to this function
var lastShowIndexRead = PropertiesService.getScriptProperties().getProperty('LastShowIndexRead');
// if lastShowIndexRead is NULL then lastShowIndexRead is 0, else parse lastShowIndexRead as an integer
lastShowIndexRead = (lastShowIndexRead === null ? 0 : parseInt(lastShowIndexRead));
// this variable is the offset value used in the UrlFetch when getting each episode for each podcast
var lastEpisodeOffset = PropertiesService.getScriptProperties().getProperty('lastEpisodeOffset');
lastEpisodeOffset = (lastEpisodeOffset === null ? 0 : parseInt(lastEpisodeOffset));
//we'll now delete the property so it won't be used again on another call
PropertiesService.getScriptProperties().deleteProperty('lastShowIndexRead');
PropertiesService.getScriptProperties().deleteProperty('lastEpisodeOffset');
if (lastEpisodeOffset === 0 && lastShowIndexRead === 0) {
sOutput.getRange("A2:L").clear(); // only clear the spreadsheet on the first execution run of this function
};
let j = sOutput.getLastRow();
var clientId = <your clientID>;
var clientSecret = <your clientSecret>;
var url = "https://accounts.spotify.com/api/token";
var params = {
method: "post",
headers: {"Authorization" : "Basic " + Utilities.base64Encode(clientId + ":" + clientSecret)},
payload: {grant_type: "client_credentials"},
};
var res = UrlFetchApp.fetch(url, params);
var obj = JSON.parse(res.getContentText());
var token = obj.access_token;
Logger.log("token = " + token);
var parameters = {
method: "GET",
headers: {
"Authorization" : "Bearer " + token
},
json : true,
};
for (var showIndex = lastShowIndexRead; showIndex < arrPodcasts.length; showIndex++) {
const show = arrPodcasts[showIndex][0];
if (show !== '') {
let offset = lastEpisodeOffset;
lastEpisodeOffset = 0;
var getPodcast = "https://api.spotify.com/v1/shows/" + show + "/episodes";
var fetchPodcast = UrlFetchApp.fetch(getPodcast, parameters);
var totEps = JSON.parse(fetchPodcast.getContentText()).total;
Logger.log("total episodes = " + totEps);
let n = Math.floor(totEps/50) + 1; // determine number of loops needed to retrieve all episodes for the podcast
Logger.log("We need to loop " + n + " times");
var outputArray = [];
do {
var podcasts = `https://api.spotify.com/v1/shows/${show}/episodes?offset=${offset}&limit=50&market=GB`;
Logger.log(podcasts);
Logger.log("Offset = " + offset);
var obj = JSON.parse(UrlFetchApp.fetch("https://api.spotify.com/v1/shows/" + show + "/?market=gb", parameters).getContentText());
var nameShow = obj.name;
var publisher = obj.publisher;
Logger.log(nameShow);
try {
var podcast = UrlFetchApp.fetch(podcasts, parameters);
}
catch(err) {
Logger.log("Move onto the next podcast");
}
var object = JSON.parse(podcast.getContentText());
Logger.log("Offset = " + offset);
if ('items' in object) {
for (var i = 0; i < object.items.length; i++) {
offset++;
var episode = object.items[i];
if (episode !== null) {
Logger.log(j + ') ' + episode.name + '\n'+ episode.release_date);
j = j + 1; // index the episodes for each podcast
//var rowStart = sOutput.getLastRow() + 1;
//sOutput.getRange(rowStart, 1, 1, 12).setValues([[nameShow, publisher, episode.name, episode.release_date, episode.description, episode.type, episode.explicit, episode.duration_ms, "https://open.spotify.com/embed/episode/" + episode.id, episode.images[0].url, showIndex, offset]]);
outputArray.push([nameShow, publisher, episode.name, episode.release_date, episode.description, episode.type, episode.explicit, episode.duration_ms, "https://open.spotify.com/embed/episode/" + episode.id, episode.images[0].url, showIndex, offset]);
}
}
} else {
console.log(`ERROR: no items found in object`);
}
var currentTime = new Date();
// if execution time exceeds define MAX_TIME and there are still more episodes to retrieve, then build a new trigger and resume in the next trigger rather than letting the current execution time out
if (offset < totEps && currentTime.getTime() - startTime.getTime() > MAX_TIME) {
PropertiesService.getScriptProperties().setProperty('LastShowIndexRead', showIndex.toString());
PropertiesService.getScriptProperties().setProperty('lastEpisodeOffset', offset.toString());
ScriptApp.newTrigger('getEpisodes')
.timeBased()
.at(new Date(new Date().getTime() + (20 * 1000))) // 20 seconds from NOW
.create();
console.log(`Trigger created to resume ${'getEpisodes'} in 20 seconds...`);
return; //exit before the time-out
}
} while (offset < totEps);
var rowStart = sOutput.getLastRow() + 1;
sOutput.getRange(rowStart, 1, outputArray.length, outputArray[0].length).setValues(outputArray);
}
}
}
Hope someone finds this useful!