Skip to content

Instantly share code, notes, and snippets.

@elijahr
Last active October 23, 2024 22:46
Show Gist options
  • Save elijahr/bab853c48077bdfb4c9da9e181b49684 to your computer and use it in GitHub Desktop.
Save elijahr/bab853c48077bdfb4c9da9e181b49684 to your computer and use it in GitHub Desktop.
Parse the Expandable fields for all Stripe API resources.
function upperCamelToTitlePhrase(upperCamel) {
console.debug(`Converting upper camel case to phrase: ${upperCamel}`);
return upperCamel.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^[a-z]/, firstChar => firstChar.toUpperCase());
}
function pluralize(str) {
console.debug(`Pluralizing string: ${str}`);
return str.endsWith('s') ? str : str + 's';
}
function jsonWithTrailingCommas(obj) {
console.debug('Converting object to JSON with trailing commas:', obj);
const jsonString = JSON.stringify(obj, null, 4);
const lines = jsonString.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
if (lines[i].trim().endsWith('}') || lines[i].trim().endsWith('"') || lines[i].trim().endsWith(']')) {
lines[i] += ',';
}
}
return lines.join('\n');
}
function useProgressBar({totalCount}) {
console.debug('Creating or replacing progress bar');
const existingProgressBarContainer = document.getElementById('progress-bar-container');
if (existingProgressBarContainer) {
existingProgressBarContainer.remove();
}
// Create and style the progress bar
const progressBarContainer = document.createElement('div');
progressBarContainer.id = 'progress-bar-container';
progressBarContainer.style.position = 'fixed';
progressBarContainer.style.bottom = '36px';
progressBarContainer.style.right = '100px';
progressBarContainer.style.width = '300px';
progressBarContainer.style.height = '36px';
progressBarContainer.style.backgroundColor = '#f3f3f3';
progressBarContainer.style.border = '1px solid #ccc';
progressBarContainer.style.borderRadius = '5px';
progressBarContainer.style.zIndex = '1000';
// Create and style the label for the progress bar
const progressBarLabel = document.createElement('div');
progressBarLabel.textContent = 'Loading Documentation... 0%';
progressBarLabel.style.position = 'absolute';
progressBarLabel.style.top = '-25px';
progressBarLabel.style.left = '0';
progressBarLabel.style.width = '100%';
progressBarLabel.style.textAlign = 'center';
progressBarLabel.style.fontSize = '14px';
progressBarLabel.style.color = '#fff';
progressBarLabel.style.fontWeight = 'bold';
progressBarContainer.appendChild(progressBarLabel);
const progressBar = document.createElement('div');
progressBar.style.height = '100%';
progressBar.style.width = '0%';
progressBar.style.backgroundColor = '#4caf50';
progressBar.style.transition = 'width 0.2s';
progressBarContainer.appendChild(progressBar);
document.body.appendChild(progressBarContainer);
let count = 0;
let percentage = 0;
return () => {
count++;
percentage = 100 * (count / totalCount);
progressBar.style.width = `${percentage}%`;
progressBarLabel.textContent = `Loading Documentation... ${Math.round(percentage)}%`;
};
}
function findSectionForResourceName(resourceName) {
const headerContent = upperCamelToTitlePhrase(resourceName);
const mainLink = Array.from(document.querySelectorAll('main [data-testid^="api-section-group"] a[data-testid^="api-section-"]')).filter(a => a.innerText === headerContent || a.innerText === pluralize(headerContent))[0];
if (resourceName === 'Subscription') debugger
if (!mainLink) {
console.error(`Could not find link for ${headerContent}`);
throw new Error(`Could not find link for ${headerContent}`);
}
const section = mainLink.closest('[data-testid^="api-section-group"]');
if (!section) {
console.error(`Could not find section for ${headerContent}`);
throw new Error(`Could not find section for ${headerContent}`);
}
return section;
}
async function loadSection(section) {
while (section.querySelector('ul.method-list') === null) {
console.log(`Loading section ${section.id}`);
const loadButton = section.querySelector('[data-testid="api-section-group-load-button"]');
if (!loadButton) {
console.error(`Could not find load button for ${section.id}`);
throw new Error(`Could not find load button for ${section.id}`);
}
loadButton.click();
await new Promise(resolve => setTimeout(resolve, 1000)); // Adjust delay as needed
}
}
async function getExpandableFields({tickProgress, resourceNames}) {
const result = {};
for (const resourceName of resourceNames) {
result[resourceName] = [];
const section = findSectionForResourceName(resourceName);
await loadSection(section);
const methodList = section.querySelector('ul.method-list');
methodList.querySelectorAll('li.ApiReference-Element').forEach(li => {
li.querySelectorAll('span').forEach(span => {
if (span.textContent === 'Expandable' && span.children.length === 0) {
const labelElement = span.parentElement.querySelector('span:first-child');
if (labelElement) {
if (labelElement.textContent === 'line_items') {
debugger
}
result[resourceName].push(labelElement.textContent);
}
}
});
});
tickProgress();
}
return result;
}
function findDefaultExpandedPaths(obj, currentPath = '') {
const paths = [];
for (const key in obj) {
if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
if (obj[key].hasOwnProperty('object')) {
if (obj[key].object === 'list') {
paths.push(...findDefaultExpandedPaths(obj[key].data, newPath + ".data"));
} else {
paths.push(newPath);
}
}
paths.push(...findDefaultExpandedPaths(obj[key], newPath));
}
}
return paths;
}
async function getDefaultExpandedFields({tickProgress, resourceNames}) {
const result = {};
for (const resourceName of resourceNames) {
result[resourceName] = [];
const section = findSectionForResourceName(resourceName);
await loadSection(section)
const code = section.querySelector('pre code');
const exampleObject = JSON.parse(code.textContent);
result[resourceName] = findDefaultExpandedPaths(exampleObject);
tickProgress();
}
return result;
}
async function setup({totalCount}) {
const tickProgress = useProgressBar({totalCount});
// Scroll up and down to ensure all header contents are loaded
window.scrollTo(0, document.body.scrollHeight);
await new Promise(resolve => setTimeout(resolve, 2000));
window.scrollTo(0, 0);
await new Promise(resolve => setTimeout(resolve, 2000));
return tickProgress;
}
async function main() {
const resourceNames = [
"Account",
"AccountLink",
"ApplicationFee",
"Balance",
"Charge",
"Coupon",
"Customer",
"Dispute",
"Invoice",
"PaymentIntent",
"Payout",
"PromotionCode",
"Refund",
"Subscription",
"Token",
"Transfer",
];
const tickProgress = await setup({totalCount: resourceNames.length * 2});
console.log("default_expanded_fields_by_stripe_resource = " + jsonWithTrailingCommas(await getDefaultExpandedFields({tickProgress, resourceNames})));
console.log("expandable_fields_by_stripe_resource = " + jsonWithTrailingCommas(await getExpandableFields({tickProgress, resourceNames})));
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment