@johnbillion/ghsvg
Version:
Create an SVG of your GitHub Sponsors
246 lines (234 loc) • 6.41 kB
text/typescript
import * as axios from 'axios';
import * as camelcaseKeys from 'camelcase-keys';
import * as fs from 'fs';
export async function request(
type: string,
url: string,
query?: string
): Promise<Record<string, any>> {
try {
const config = {
headers: {
Authorization: `bearer ${process.env['GITHUB_TOKEN']}`
}
};
const response =
type === 'get'
? await axios.default.get(url, config)
: await axios.default.post(url, {query: query}, config);
if (response.data.error) {
console.error(`API failure: ${response.data.error}`);
process.exit(1);
}
return response.data;
} catch (error) {
console.error(`API failure: ${url}`);
process.exit(1);
}
}
export async function getUserType(username: string): Promise<string> {
return (
await request('get', `https://api.github.com/users/${username}`)
).type.toLowerCase();
}
export async function getFields(type: string): Promise<Record<string, any>[]> {
const response = await request(
'post',
'https://api.github.com/graphql',
`
{
__type(name: "${type}") {
fields {
name
description
type {
ofType {
kind
}
}
args {
name
defaultValue
}
}
}
}
`
);
return response.data.__type.fields;
}
export async function getAccountFragment(
avatar_size?: number
): Promise<string> {
const userFields = (await getFields('User'))
.map((field: Record<string, any>) => {
if (avatar_size && field.name === 'avatarUrl') {
return `avatarUrl(size: ${avatar_size})`;
} else if (field.args.length || field.type.ofType?.kind !== 'SCALAR') {
return;
}
return field.name;
})
.filter(Boolean);
const organizationFields = (await getFields('Organization'))
.map((field: Record<string, any>) => {
if (avatar_size && field.name === 'avatarUrl') {
return `avatarUrl(size: ${avatar_size})`;
} else if (
field.args.length ||
field.type.ofType?.kind !== 'SCALAR'
) {
return;
}
const fieldsToRemove = [
'membersCanForkPrivateRepositories',
'webCommitSignoffRequired',
]
if (fieldsToRemove.includes(field.name)) {
return;
}
return field.name;
})
.filter(Boolean);
return `
... on User {
${userFields.join('\n')}
}
... on Organization {
${organizationFields.join('\n')}
}
`;
}
export async function getGithubSponsorsHelper(
ghsOptions: Record<string, any>,
results: any[] = [],
cursor: undefined | string = undefined
): Promise<Record<string, any>[]> {
const args = ['first: 100'];
if (cursor) {
args.push(`after:"${cursor}"`);
}
const query = `
{
${ghsOptions.nodeType}(login: "${ghsOptions.username}") {
... on Sponsorable {
sponsorshipsAsMaintainer(${args.join(', ')}) {
totalCount
pageInfo {
endCursor
hasNextPage
}
nodes {
tier {
id
isOneTime
isCustomAmount
monthlyPriceInDollars
}
sponsorEntity {
${ghsOptions.accountFragment}
}
}
}
}
}
}`;
const response = await request(
'post',
'https://api.github.com/graphql',
query
);
const sponsorData: Record<string, any> =
response.data[ghsOptions.nodeType].sponsorshipsAsMaintainer;
const sponsorNodes: Record<string, any>[] = sponsorData.nodes
.sort((a: Record<string, any>, b: Record<string, any>) => {
return b?.tier?.monthlyPriceInDollars - a?.tier?.monthlyPriceInDollars;
})
.map((sponsorRecord: Record<string, any>) => {
if (sponsorRecord.tier) {
const [minimum, maximum]: number[] = sponsorRecord.tier.isOneTime
? ghsOptions.oneTimeTiersRange
: sponsorRecord.tier.isCustomAmount
? ghsOptions.customAmountRange
: ghsOptions.monthlyTiersRange;
if (
sponsorRecord.tier.monthlyPriceInDollars < minimum ||
sponsorRecord.tier.monthlyPriceInDollars > maximum
) {
return false;
}
}
return sponsorRecord.sponsorEntity;
})
.filter(Boolean);
results.push(...sponsorNodes);
if (!ghsOptions.quiet) {
process.stdout.write(
`Fetching sponsors ${Math.floor(
(results.length / sponsorData.totalCount) * 100
)} % complete... \r`
);
}
if (sponsorData['pageInfo']['hasNextPage']) {
await getGithubSponsorsHelper(
ghsOptions,
results,
sponsorData['pageInfo']['endCursor']
);
}
return results;
}
export async function getGithubSponsorTiers(
username: string
): Promise<Record<string, any>[]> {
const tier_fields = await getFields('SponsorsTier');
const response = await request(
'post',
'https://api.github.com/graphql',
`
{
user(login: "${username}") {
sponsorsListing {
tiers(first: 20) {
nodes {
${tier_fields.join('\n')}
}
}
}
}
}
`
);
return response.data.user.sponsorsListing.tiers.nodes;
}
export async function getGithubSponsors(
ghsOptions: Record<string, any>
): Promise<Record<string, any>[]> {
ghsOptions.nodeType = await getUserType(ghsOptions.username);
ghsOptions.accountFragment = await getAccountFragment(
ghsOptions.svgImageWidth
);
return await getGithubSponsorsHelper(ghsOptions);
}
export async function getUser(username: string): Promise<Record<string, any>> {
return camelcaseKeys.default(
await request('get', `https://api.github.com/users/${username}`)
);
}
export async function getOtherSponsors(
filepath: string
): Promise<Record<string, string>[]> {
if (!fs.existsSync(filepath)) {
return [];
}
const sponsors = fs
.readFileSync(filepath)
.toString()
.split(/\r?\n/)
.filter(Boolean);
return await Promise.all(
sponsors.map(async sponsor => {
return await getUser(sponsor);
})
);
}