Skip to content

Commit 2c92216

Browse files
πŸ› fix: Fix deps
1 parent de10004 commit 2c92216

File tree

14 files changed

+883
-67
lines changed

14 files changed

+883
-67
lines changed

β€Ž.eslintignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ logs
2727

2828
# misc
2929
# add other ignore file below
30-
lib
30+
**/**/makeQuery.ts

β€Žapi/sponsor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ImageResponse } from '@vercel/og';
2-
import 'dotenv/config';
3-
import { fetchSponsors } from 'sponsorkit';
2+
3+
import { fetchSponsors } from '@/services/sponsorkit';
44

55
import cors from '../lib/cors';
66
import Sponsor from '../src/Sponsor';

β€Žpackage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"leva": "latest",
6767
"lodash-es": "^4",
6868
"lucide-react": "latest",
69+
"node-html-parser": "^6.1.13",
6970
"polished": "^4",
7071
"query-string": "^8",
7172
"react-layout-kit": "^1",
@@ -77,7 +78,6 @@
7778
"remark-slug": "^7",
7879
"remark-toc": "^8",
7980
"simple-icons": "^10.0.0",
80-
"sponsorkit": "^0.9.3",
8181
"swr": "^2.2.4",
8282
"url-join": "^5",
8383
"use-merge-value": "^1",

β€Žsrc/Sponsor/demos/data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Sponsorship } from 'sponsorkit';
1+
import { Sponsorship } from '@/services/sponsorkit/types';
22

33
const data: Sponsorship[] = [
44
{

β€Žsrc/Sponsor/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { CSSProperties, FC } from 'react';
2-
import { Sponsorship } from 'sponsorkit';
2+
3+
import { Sponsorship } from '@/services/sponsorkit/types';
34

45
import { Avatar } from './Avatar';
56
import { DEFAULT_AVATAR_SIZE } from './const';

β€Žsrc/Sponsor/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Sponsorship } from 'sponsorkit';
1+
import { Sponsorship } from '@/services/sponsorkit/types';
22

33
import {
44
DEFAULT_AVATAR_SIZE,

β€Žsrc/Tabs/index.tsx

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { parse } from 'node-html-parser';
2+
3+
import type { Sponsorship } from '../types';
4+
5+
export interface GetPastSponsorsOptions {
6+
/**
7+
* @default false
8+
*/
9+
includePrivate?: boolean;
10+
}
11+
12+
function pickSponsorsInfo(html: string): Sponsorship[] {
13+
const root = parse(html);
14+
const baseDate = new Date('2000-1-1');
15+
const sponsors = root.querySelectorAll('div').map((el) => {
16+
const isPublic = el.querySelector('img');
17+
const name = isPublic ? isPublic?.getAttribute('alt')?.replace('@', '') : 'Private Sponsor';
18+
const avatarUrl = isPublic ? isPublic?.getAttribute('src') : '';
19+
const login = isPublic
20+
? el.querySelector('a')?.getAttribute('href')?.replace('/', '')
21+
: undefined;
22+
const type = el
23+
.querySelector('a')
24+
?.getAttribute('data-hovercard-type')
25+
?.replace(/^\S/, (s) => s.toUpperCase());
26+
27+
return {
28+
createdAt: baseDate.toUTCString(),
29+
isOneTime: undefined,
30+
monthlyDollars: -1,
31+
privacyLevel: isPublic ? 'PUBLIC' : 'PRIVATE',
32+
sponsor: {
33+
__typename: undefined,
34+
avatarUrl,
35+
linkUrl: `https://github.com/${name}`,
36+
login,
37+
name,
38+
type,
39+
},
40+
tierName: undefined,
41+
} as Sponsorship;
42+
});
43+
44+
return sponsors;
45+
}
46+
47+
export async function getPastSponsors(username: string): Promise<Sponsorship[]> {
48+
const allSponsors: Sponsorship[] = [];
49+
let newSponsors = [];
50+
let cursor = 1;
51+
52+
do {
53+
const res = await fetch(
54+
`https://github.com/sponsors/${username}/sponsors_partial?filter=inactive&page=${cursor++}`,
55+
{ method: 'GET' },
56+
);
57+
const content = await res.text();
58+
newSponsors = pickSponsorsInfo(content);
59+
allSponsors.push(...newSponsors);
60+
} while (newSponsors.length);
61+
62+
return allSponsors;
63+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { Provider, SponsorkitConfig, Sponsorship } from '../types';
2+
import { getPastSponsors } from './get-past-sponsors';
3+
import { makeQuery } from './makeQuery';
4+
5+
const API = 'https://api.github.com/graphql';
6+
7+
export async function fetchGitHubSponsors(
8+
token: string,
9+
login: string,
10+
type: string,
11+
config: SponsorkitConfig,
12+
): Promise<Sponsorship[]> {
13+
if (!token) throw new Error('GitHub token is required');
14+
if (!login) throw new Error('GitHub login is required');
15+
if (!['user', 'organization'].includes(type))
16+
throw new Error('GitHub type must be either `user` or `organization`');
17+
18+
const sponsors: Sponsorship[] = [];
19+
let cursor;
20+
21+
do {
22+
const query = makeQuery(login, type, cursor);
23+
const res = await fetch(API, {
24+
body: JSON.stringify({ query }),
25+
headers: {
26+
'Authorization': `bearer ${token}`,
27+
'Content-Type': 'application/json',
28+
},
29+
method: 'POST',
30+
});
31+
32+
const data = await res.json();
33+
34+
if (!data) throw new Error(`Get no response on requesting ${API}`);
35+
else if (data.errors?.[0]?.type === 'INSUFFICIENT_SCOPES')
36+
throw new Error('Token is missing the `read:user` and/or `read:org` scopes');
37+
else if (data.errors?.length)
38+
throw new Error(`GitHub API error:\n${JSON.stringify(data.errors, null, 2)}`);
39+
40+
sponsors.push(...(data.data[type].sponsorshipsAsMaintainer.nodes || []));
41+
if (data.data[type].sponsorshipsAsMaintainer.pageInfo.hasNextPage)
42+
cursor = data.data[type].sponsorshipsAsMaintainer.pageInfo.endCursor;
43+
else cursor = undefined;
44+
} while (cursor);
45+
46+
const processed = sponsors.map(
47+
(raw: any): Sponsorship => ({
48+
createdAt: raw.createdAt,
49+
isOneTime: raw.tier.isOneTime,
50+
monthlyDollars: raw.tier.monthlyPriceInDollars,
51+
privacyLevel: raw.privacyLevel,
52+
sponsor: {
53+
...raw.sponsorEntity,
54+
__typename: undefined,
55+
linkUrl: `https://github.com/${raw.sponsorEntity.login}`,
56+
type: raw.sponsorEntity.__typename,
57+
},
58+
tierName: raw.tier.name,
59+
}),
60+
);
61+
62+
if (config.includePastSponsors) {
63+
try {
64+
processed.push(...(await getPastSponsors(login)));
65+
} catch (error) {
66+
console.error('Failed to fetch past sponsors:', error);
67+
}
68+
}
69+
70+
return processed;
71+
}
72+
73+
export const GitHubProvider: Provider = {
74+
fetchSponsors(config) {
75+
return fetchGitHubSponsors(
76+
config.github?.token || config.token!,
77+
config.github?.login || config.login!,
78+
config.github?.type || 'user',
79+
config,
80+
);
81+
},
82+
name: 'github',
83+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const graphql = String.raw;
2+
3+
export function makeQuery(login: string, type: string, cursor?: string) {
4+
return graphql`{
5+
${type}(login: "${login}") {
6+
sponsorshipsAsMaintainer(first: 100${cursor ? ` after: "${cursor}"` : ''}) {
7+
totalCount
8+
pageInfo {
9+
endCursor
10+
hasNextPage
11+
}
12+
nodes {
13+
createdAt
14+
privacyLevel
15+
tier {
16+
name
17+
isOneTime
18+
monthlyPriceInCents
19+
monthlyPriceInDollars
20+
}
21+
sponsorEntity {
22+
__typename
23+
...on Organization {
24+
login
25+
name
26+
avatarUrl
27+
websiteUrl
28+
}
29+
...on User {
30+
login
31+
name
32+
avatarUrl
33+
websiteUrl
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}`;
40+
}

0 commit comments

Comments
Β (0)