forked from wangrunlin/github-achievements-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
126 lines (108 loc) · 3.56 KB
/
index.ts
File metadata and controls
126 lines (108 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { UsageResponse, AchievementsResponse } from './types';
// 创建 CORS 头部
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
// 创建 JSON 响应的辅助函数
function createJsonResponse(data: any, status = 200, additionalHeaders = {}) {
return new Response(JSON.stringify(data, null, 2), {
status,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
...corsHeaders,
...additionalHeaders,
},
});
}
// 创建错误响应的辅助函数
function createErrorResponse(message: string, status = 500) {
return new Response(message, {
status,
headers: corsHeaders,
});
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const username = url.pathname.split('/')[1];
if (!username) {
const usage: UsageResponse = {
description: 'GitHub Achievements API - 获取用户的 GitHub 成就信息',
author: {
name: 'Leo Wang',
github: 'https://github.com/wangrunlin',
},
repository: 'https://github.com/wangrunlin/github-achievements-api',
usage: {
endpoint: `${url.origin}/<github_username>`,
example: `${url.origin}/wangrunlin`,
},
response: {
total: {
raw: '成就总数(不计算等级)',
weighted: '成就总数(计算等级)',
},
achievements: [
{
type: '成就类型',
tier: '成就等级(若无等级则为1)',
},
],
},
};
return createJsonResponse(usage);
}
try {
const cacheKey = `${url.origin}/cache/${username}`;
const cache = caches.default;
const skipCache = url.searchParams.has('nocache');
let response = skipCache ? null : await cache.match(cacheKey);
if (!response) {
const githubUrl = `https://github.com/${username}`;
const githubResponse = await fetch(githubUrl);
if (!githubResponse.ok) {
return createErrorResponse(`Failed to fetch GitHub achievements: ${githubResponse.statusText}`, githubResponse.status);
}
const html = await githubResponse.text();
const achievementsSection = html.match(/<div class="d-flex flex-wrap">[\s\S]*?<\/div>/);
if (!achievementsSection) {
return createJsonResponse({ total: 0, achievements: [] });
}
const achievements: { type: string; tier?: number; image?: string }[] = [];
const pattern = new RegExp(
`<a[^>]*href="/${username}\\?achievement=([^&]+)[^>]*>\\s*<img\\s+src="([^"]+)"[^>]*>.*?(?:class="Label[^>]*achievement-tier-label[^>]*>x(\\d+))?(?:</span>)?</a>`,
'gs'
);
let match;
while ((match = pattern.exec(achievementsSection[0])) !== null) {
const [, type, image, tier] = match;
achievements.push({
type: type.trim(),
tier: tier ? parseInt(tier) : 1,
image
});
}
const rawTotal = achievements.length;
const weightedTotal = achievements.reduce((sum, { tier = 1 }) => sum + tier, 0);
const result: AchievementsResponse = {
total: {
raw: rawTotal,
weighted: weightedTotal,
},
achievements: achievements.sort((a, b) => (b.tier || 1) - (a.tier || 1)),
};
response = createJsonResponse(result, 200, {
'Cache-Control': skipCache ? 'no-store' : 'public, max-age=3600',
});
if (!skipCache) {
ctx.waitUntil(cache.put(new Request(cacheKey), response.clone()));
}
}
return response;
} catch (error: unknown) {
return createErrorResponse(`Error: ${error}`);
}
},
} satisfies ExportedHandler<Env>;