Skip to content

Commit

Permalink
πŸ› fix: Fix deps
Browse files Browse the repository at this point in the history
  • Loading branch information
canisminor1990 committed Apr 30, 2024
1 parent de10004 commit 2c92216
Show file tree
Hide file tree
Showing 14 changed files with 883 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ logs

# misc
# add other ignore file below
lib
**/**/makeQuery.ts
4 changes: 2 additions & 2 deletions api/sponsor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ImageResponse } from '@vercel/og';
import 'dotenv/config';
import { fetchSponsors } from 'sponsorkit';

import { fetchSponsors } from '@/services/sponsorkit';

import cors from '../lib/cors';
import Sponsor from '../src/Sponsor';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"leva": "latest",
"lodash-es": "^4",
"lucide-react": "latest",
"node-html-parser": "^6.1.13",
"polished": "^4",
"query-string": "^8",
"react-layout-kit": "^1",
Expand All @@ -77,7 +78,6 @@
"remark-slug": "^7",
"remark-toc": "^8",
"simple-icons": "^10.0.0",
"sponsorkit": "^0.9.3",
"swr": "^2.2.4",
"url-join": "^5",
"use-merge-value": "^1",
Expand Down
2 changes: 1 addition & 1 deletion src/Sponsor/demos/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Sponsorship } from 'sponsorkit';
import { Sponsorship } from '@/services/sponsorkit/types';

const data: Sponsorship[] = [
{
Expand Down
3 changes: 2 additions & 1 deletion src/Sponsor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CSSProperties, FC } from 'react';
import { Sponsorship } from 'sponsorkit';

import { Sponsorship } from '@/services/sponsorkit/types';

import { Avatar } from './Avatar';
import { DEFAULT_AVATAR_SIZE } from './const';
Expand Down
2 changes: 1 addition & 1 deletion src/Sponsor/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Sponsorship } from 'sponsorkit';
import { Sponsorship } from '@/services/sponsorkit/types';

import {
DEFAULT_AVATAR_SIZE,
Expand Down
60 changes: 0 additions & 60 deletions src/Tabs/index.tsx

This file was deleted.

63 changes: 63 additions & 0 deletions src/services/sponsorkit/github/get-past-sponsors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { parse } from 'node-html-parser';

import type { Sponsorship } from '../types';

export interface GetPastSponsorsOptions {
/**
* @default false
*/
includePrivate?: boolean;
}

function pickSponsorsInfo(html: string): Sponsorship[] {
const root = parse(html);
const baseDate = new Date('2000-1-1');
const sponsors = root.querySelectorAll('div').map((el) => {
const isPublic = el.querySelector('img');
const name = isPublic ? isPublic?.getAttribute('alt')?.replace('@', '') : 'Private Sponsor';
const avatarUrl = isPublic ? isPublic?.getAttribute('src') : '';
const login = isPublic
? el.querySelector('a')?.getAttribute('href')?.replace('/', '')
: undefined;
const type = el
.querySelector('a')
?.getAttribute('data-hovercard-type')
?.replace(/^\S/, (s) => s.toUpperCase());

return {
createdAt: baseDate.toUTCString(),
isOneTime: undefined,
monthlyDollars: -1,
privacyLevel: isPublic ? 'PUBLIC' : 'PRIVATE',
sponsor: {
__typename: undefined,
avatarUrl,
linkUrl: `https://github.com/${name}`,
login,
name,
type,
},
tierName: undefined,
} as Sponsorship;
});

return sponsors;
}

export async function getPastSponsors(username: string): Promise<Sponsorship[]> {
const allSponsors: Sponsorship[] = [];
let newSponsors = [];
let cursor = 1;

do {
const res = await fetch(
`https://github.com/sponsors/${username}/sponsors_partial?filter=inactive&page=${cursor++}`,
{ method: 'GET' },
);
const content = await res.text();
newSponsors = pickSponsorsInfo(content);
allSponsors.push(...newSponsors);
} while (newSponsors.length);

return allSponsors;
}
83 changes: 83 additions & 0 deletions src/services/sponsorkit/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { Provider, SponsorkitConfig, Sponsorship } from '../types';
import { getPastSponsors } from './get-past-sponsors';
import { makeQuery } from './makeQuery';

const API = 'https://api.github.com/graphql';

export async function fetchGitHubSponsors(
token: string,
login: string,
type: string,
config: SponsorkitConfig,
): Promise<Sponsorship[]> {
if (!token) throw new Error('GitHub token is required');
if (!login) throw new Error('GitHub login is required');
if (!['user', 'organization'].includes(type))
throw new Error('GitHub type must be either `user` or `organization`');

const sponsors: Sponsorship[] = [];
let cursor;

do {
const query = makeQuery(login, type, cursor);
const res = await fetch(API, {
body: JSON.stringify({ query }),
headers: {
'Authorization': `bearer ${token}`,
'Content-Type': 'application/json',
},
method: 'POST',
});

const data = await res.json();

if (!data) throw new Error(`Get no response on requesting ${API}`);
else if (data.errors?.[0]?.type === 'INSUFFICIENT_SCOPES')
throw new Error('Token is missing the `read:user` and/or `read:org` scopes');
else if (data.errors?.length)
throw new Error(`GitHub API error:\n${JSON.stringify(data.errors, null, 2)}`);

sponsors.push(...(data.data[type].sponsorshipsAsMaintainer.nodes || []));
if (data.data[type].sponsorshipsAsMaintainer.pageInfo.hasNextPage)
cursor = data.data[type].sponsorshipsAsMaintainer.pageInfo.endCursor;
else cursor = undefined;
} while (cursor);

const processed = sponsors.map(
(raw: any): Sponsorship => ({
createdAt: raw.createdAt,
isOneTime: raw.tier.isOneTime,
monthlyDollars: raw.tier.monthlyPriceInDollars,
privacyLevel: raw.privacyLevel,
sponsor: {
...raw.sponsorEntity,
__typename: undefined,
linkUrl: `https://github.com/${raw.sponsorEntity.login}`,
type: raw.sponsorEntity.__typename,
},
tierName: raw.tier.name,
}),
);

if (config.includePastSponsors) {
try {
processed.push(...(await getPastSponsors(login)));
} catch (error) {
console.error('Failed to fetch past sponsors:', error);
}
}

return processed;
}

export const GitHubProvider: Provider = {
fetchSponsors(config) {
return fetchGitHubSponsors(
config.github?.token || config.token!,
config.github?.login || config.login!,
config.github?.type || 'user',
config,
);
},
name: 'github',
};
40 changes: 40 additions & 0 deletions src/services/sponsorkit/github/makeQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const graphql = String.raw;

export function makeQuery(login: string, type: string, cursor?: string) {
return graphql`{
${type}(login: "${login}") {
sponsorshipsAsMaintainer(first: 100${cursor ? ` after: "${cursor}"` : ''}) {
totalCount
pageInfo {
endCursor
hasNextPage
}
nodes {
createdAt
privacyLevel
tier {
name
isOneTime
monthlyPriceInCents
monthlyPriceInDollars
}
sponsorEntity {
__typename
...on Organization {
login
name
avatarUrl
websiteUrl
}
...on User {
login
name
avatarUrl
websiteUrl
}
}
}
}
}
}`;
}
51 changes: 51 additions & 0 deletions src/services/sponsorkit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { GitHubProvider } from './github';
import { OpenCollectiveProvider } from './opencollective';
import type { Provider, ProviderName, SponsorkitConfig } from './types';

export * from './github';

export const ProvidersMap = {
github: GitHubProvider,
opencollective: OpenCollectiveProvider,
};

export function guessProviders(config: SponsorkitConfig) {
const items: ProviderName[] = [];
if (config.github && config.github.login) items.push('github');

if (config.patreon && config.patreon.token) items.push('patreon');

if (
config.opencollective &&
(config.opencollective.id || config.opencollective.slug || config.opencollective.githubHandle)
)
items.push('opencollective');

if (config.afdian && config.afdian.userId && config.afdian.token) items.push('afdian');

// fallback
if (!items.length) items.push('github');

return items;
}

export function resolveProviders(names: (ProviderName | Provider)[]) {
return [...new Set(names)].map((i) => {
if (typeof i === 'string') {
// @ts-ignore
const provider = ProvidersMap[i];
if (!provider) throw new Error(`Unknown provider: ${i}`);
return provider;
}
return i;
});
}

export async function fetchSponsors(config: SponsorkitConfig) {
const providers = resolveProviders(guessProviders(config));
const sponsorships = await Promise.all(
providers.map((provider) => provider.fetchSponsors(config)),
);

return sponsorships.flat(1);
}
Loading

0 comments on commit 2c92216

Please sign in to comment.