Published
Edited
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
async function queryAPI(route, params = {}) {
// Strip any leading dashes, as those would cause the query to fail.
route = route.replace(/^\/+/, '');
// Turns the params object into a series of query arguments and properly escapes values.
// See https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams for more examples.
params = new URLSearchParams(params);
const url = `https://api.genius.com/${route}?${params}`;
const response = await fetchp(url, {
// Cache responses aggressively, so that previous API requests don't get sent again
// when rerunning a query.
cache: 'force-cache',
// docs says it's better to access with 'Bearer' and the accessToken through a header
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return response.json();
}
Insert cell
Insert cell
// Example search result:
search('swag')
Insert cell
Insert cell
results = {
// Comment the following line to run a new search.
return yield FileAttachment('results.json').json();
// Some options for convenience:
// - the maximum number of pages to retrieve (set to 0 for unlimited)
const limit = 3;
// - artificial delay between requests (see below for details)
const delay = 500;
// - the search term
const query = 'swag';
const results = [];
let page = 1, result;
do {
// Get the next set of results ...
result = await search(query, page++);
// ... and add them to the pile.
results.push(...result.response.hits);
// Not strictly necessary, but we need some way to break the loop when this cell
// gets invalidated.
// Alternatively we could have used the invalidation promise to set (and check) an "aborted" variable.
yield results;
// The Genius API has a rate limit of 5000 requests per day.
// This delay here prevents bursts (in case there's also a limit per minute) and allows us
// to react/abort if we somehow messed up, before tons of requests get sent.
await Promises.delay(delay);
} while(
// Allows to retrieve a limited number of pages.
(!limit || page <= limit)
// Just a precaution, because the fetch itself would have likely failed already.
&& result.meta.status === 200
// We can actually get less than {per_page} results on a page, likely because some results
// are protected. It's unfortunate that Genius' API doesn't account for those in its paging.
&& result.response.hits.length
);
}
Insert cell
Insert cell
Insert cell
Insert cell
// Let's pull out the information that we want to display in the UI, while also keeping the original data.
swagResults = results.map(({result}) => ({
artist: result.primary_artist.name,
title: result.title,
year: result.release_date_components?.year ?? null,
result
}))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
selectedTitle
Insert cell
Insert cell
// our parser function that uses cors and the url to grab the element from the dom and bring into the notebook
parseLyrics = async function(url) {
const markup = await fetchp(url).then(r => r.text());
const data = new DOMParser().parseFromString(markup, 'text/html');
// This attribute selector lets us target the text container directly.
const root = data.querySelector('[data-lyrics-container="true"]');

// A few convenience functions:
// Creates a text node that contains a line break (actual newline, not the <br> tag).
const nl = () => document.createTextNode('\n');
// .querySelectorAll returns a "live" collection that changes as we add and remove elements.
// By turning it into an array we ensure that it remains unchanged, so that we can safely
// modify the DOM while iterating over the list's elements.
const selectAll = tag => Array.from(root.querySelectorAll(tag));
// Replace all <br> tags with newlines.
for(const n of selectAll('br')) n.replaceWith(nl());
// Insert a newline after each <p>.
for(const n of selectAll('p')) n.after(nl(), nl());
return root.textContent;
}
Insert cell
// And run it over the the song:
// lyrics = parseLyrics(songToParse)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
souljaBoySongs = [
{title: "Booty Got Swag", lyrics: bootygotswagConcat, swagCount: (bootygotswag.match(/swag/g) || []).length, freq: (((bootygotswag.match(/swag/g) || []).length)/(bootygotswagConcat.length))},
{title: "Broad Day [Swag]", lyrics: broaddayswagConcat, swagCount: (broaddayswag.match(/swag/g) || []).length, totalCount: broaddayswag.length, concatLength: broaddayswagConcat.length, freq: (((broaddayswag.match(/swag/g) || []).length)/(broaddayswagConcat.length))},
{title: "Cheat Code Swag", lyrics: cheatcodeswagConcat, swagCount: (cheatcodeswag.match(/swag | Swag/g) || []).length, freq: (((cheatcodeswag.match(/swag | Swag/g) || []).length)/(cheatcodeswagConcat.length))},
{title: "Da Swag", lyrics: daswagConcat, swagCount: (daswag.match(/swag | Swag/g) || []).length, freq: (((daswag.match(/swag | Swag/g) || []).length)/(daswagConcat.length))},
{title: "I'm Swaggin'", lyrics: imswagginConcat, swagCount: (imswaggin.match(/swag | Swag/g) || []).length, freq: (((imswaggin.match(/swag | Swag/g) || []).length)/(imswagginConcat.length))},
{title: "Lingo (Watch Me Swag/Juice)", lyrics: lingowatchmeswagjuiceConcat, swagCount: (lingowatchmeswagjuice.match(/swag | Swag/g) || []).length, freq: (((lingowatchmeswagjuice.match(/swag | Swag/g) || []).length)/(lingowatchmeswagjuiceConcat.length))},
{title: "Pretty Boy Swag", lyrics: prettyboyswagConcat, swagCount: (prettyboyswag.match(/swag | Swag/g) || []).length, freq: (((prettyboyswag.match(/swag | Swag/g) || []).length)/(prettyboyswagConcat.length))},
{title: "Pretty Boy Swag (Remix)", lyrics: prettyboyswagremixConcat, swagCount: (prettyboyswagremix.match(/swag | Swag/g) || []).length, freq: (((prettyboyswagremix.match(/swag | Swag/g) || []).length)/(prettyboyswagremixConcat.length))},
{title: "Swag Daddy", lyrics: swagdaddyConcat, swagCount: (swagdaddy.match(/swag | Swag/g) || []).length, freq: (((swagdaddy.match(/swag | Swag/g) || []).length)/(swagdaddyConcat.length))},
{title: "Swag Flu (Remix)", lyrics: swagfluremixConcat, swagCount: (swagfluremix.match(/swag | Swag/g) || []).length, freq: (((swagfluremix.match(/swag | Swag/g) || []).length)/(swagfluremixConcat.length))},
{title: "Swag Get At You", lyrics: swaggetatyouConcat, swagCount: (swaggetatyou.match(/swag | Swag/g) || []).length, freq: (((swaggetatyou.match(/swag | Swag/g) || []).length)/(swaggetatyouConcat.length))},
{title: "Swag OD", lyrics: swagodConcat, swagCount: (swagod.match(/swag | Swag/g) || []).length, freq: (((swagod.match(/swag | Swag/g) || []).length)/(swagodConcat.length))},
{title: "Swag on My Dick (Remix)", lyrics: swagonmydickremixConcat, swagCount: (swagonmydickremix.match(/swag | Swag/g) || []).length, freq: (((swagonmydickremix.match(/swag | Swag/g) || []).length)/(swagonmydickremixConcat.length))},
{title: "Swisher Sweet Swag", lyrics: swishersweetswagConcat, swagCount: (swishersweetswag.match(/swag | Swag/g) || []).length, freq: (((swishersweetswag.match(/swag | Swag/g) || []).length)/(swishersweetswagConcat.length))},
{title: "Trap Swag", lyrics: trapswagConcat, swagCount: (trapswag.match(/swag | Swag/g) || []).length, freq: (((trapswag.match(/swag | Swag/g) || []).length)/(trapswagConcat.length))},
{title: "Turn My Swag On", lyrics: turnmyswagonConcat, swagCount: (turnmyswagon.match(/swag | Swag/g) || []).length, freq: (((turnmyswagon.match(/swag | Swag/g) || []).length)/(turnmyswagonConcat.length))},
{title: "Turn My Swag On (Remix)", lyrics: turnmyswagonremixConcat, swagCount: (turnmyswagonremix.match(/swag | Swag/g) || []).length, freq: (((turnmyswagonremix.match(/swag | Swag/g) || []).length)/(turnmyswagonremixConcat.length))},
{title: "Video Game Swag", lyrics: videogameswagConcat, swagCount: (videogameswag.match(/swag | Swag/g) || []).length, freq: (((videogameswag.match(/swag | Swag/g) || []).length)/(videogameswagConcat.length))},
]
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more