Last active 1 month ago

适用于EO边缘函数的头像API代码

js Raw
1// 腾讯EdgeOne边缘函数:支持Gravatar(邮箱/MD5)和QQ头像(QQ号)代理,全程UTF-8编码
2addEventListener('fetch', event => {
3 event.respondWith(handleRequest(event.request))
4})
5
6/**
7 * 处理请求并代理返回对应的头像(Gravatar或QQ头像)
8 * @param {Request} request
9 */
10async function handleRequest(request) {
11 try {
12 const url = new URL(request.url)
13 const pathParts = url.pathname.split('/').filter(part => part)
14
15 // 路径格式校验:必须为/avatar/[参数]
16 if (pathParts.length !== 2 || pathParts[0] !== 'avatar') {
17 return new Response(
18 '请使用正确的路径格式:\n' +
19 '/avatar/QQ号(如/avatar/123456)\n' +
20 '/avatar/邮箱(如/avatar/user@example.com)\n' +
21 '/avatar/MD5(如/avatar/md5hashvalue)',
22 {
23 status: 400,
24 headers: { 'Content-Type': 'text/plain; charset=utf-8' }
25 }
26 )
27 }
28
29 const param = pathParts[1]
30 if (!param) {
31 return new Response(
32 '请提供参数(QQ号/邮箱/MD5),例如:\n' +
33 '/avatar/123456(QQ号)\n' +
34 '/avatar/user@example.com(邮箱)',
35 {
36 status: 400,
37 headers: { 'Content-Type': 'text/plain; charset=utf-8' }
38 }
39 )
40 }
41
42 // 1. 先判断是否为QQ号(5-13位数字)
43 if (isValidQQ(param)) {
44 return handleQQAvatar(param, url.searchParams)
45 }
46
47 // 2. 非QQ号则走原有Gravatar逻辑
48 let md5Hash;
49 if (isValidMD5(param)) {
50 md5Hash = param.toLowerCase(); // MD5统一转小写
51 } else {
52 // 视为邮箱处理
53 const processedEmail = param.trim().toLowerCase();
54 md5Hash = await md5(processedEmail);
55 }
56
57 // 构建Gravatar URL
58 const gravatarUrl = `https://www.gravatar.com/avatar/${md5Hash}${url.search}`
59 return fetchAndProxy(gravatarUrl)
60
61 } catch (error) {
62 return new Response(
63 `处理请求时出错: ${error.message}`,
64 {
65 status: 500,
66 headers: { 'Content-Type': 'text/plain; charset=utf-8' }
67 }
68 )
69 }
70}
71
72/**
73 * 处理QQ头像代理
74 * @param {string} qqNumber QQ号
75 * @param {URLSearchParams} searchParams 查询参数(如size)
76 */
77async function handleQQAvatar(qqNumber, searchParams) {
78 // QQ头像官方接口:https://q.qlogo.cn/g?b=qq&nk=QQ号&s=尺寸
79 // 映射参数:将用户传入的size转换为s(默认尺寸100)
80 const size = searchParams.get('size') || '100';
81 // 构建QQ头像URL(保留其他可能的参数,如自定义头像参数)
82 const qqAvatarUrl = new URL('https://q.qlogo.cn/g');
83 qqAvatarUrl.searchParams.set('b', 'qq'); // 固定为qq标识
84 qqAvatarUrl.searchParams.set('nk', qqNumber); // QQ号
85 qqAvatarUrl.searchParams.set('s', size); // 尺寸
86 // 透传其他参数(如用户可能传入的其他自定义参数)
87 searchParams.forEach((value, key) => {
88 if (!['size', 'b', 'nk', 's'].includes(key)) {
89 qqAvatarUrl.searchParams.set(key, value);
90 }
91 });
92
93 return fetchAndProxy(qqAvatarUrl.toString())
94}
95
96/**
97 * 通用代理请求逻辑(复用Gravatar和QQ头像的响应处理)
98 * @param {string} targetUrl 目标头像URL
99 */
100async function fetchAndProxy(targetUrl) {
101 const response = await fetch(targetUrl, {
102 method: 'GET',
103 headers: {
104 'User-Agent': 'EdgeOne Avatar Proxy',
105 'Accept': 'image/*'
106 },
107 cache: 'default'
108 });
109
110 if (!response.ok) {
111 return new Response(
112 `获取头像失败: ${response.statusText}`,
113 {
114 status: response.status,
115 headers: { 'Content-Type': 'text/plain; charset=utf-8' }
116 }
117 );
118 }
119
120 // 处理响应头(统一设置CORS、删除敏感头)
121 const headers = new Headers(response.headers);
122 headers.delete('access-control-allow-origin');
123 headers.delete('set-cookie');
124 headers.set('Access-Control-Allow-Origin', '*');
125 headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS');
126
127 // 文本类型响应补全UTF-8编码
128 const contentType = headers.get('Content-Type');
129 if (contentType && contentType.startsWith('text/')) {
130 headers.set('Content-Type', `${contentType}; charset=utf-8`);
131 }
132
133 return new Response(response.body, {
134 status: response.status,
135 headers
136 });
137}
138
139/**
140 * 验证是否为有效的QQ号(5-13位数字)
141 * @param {string} str 待验证字符串
142 * @returns {boolean}
143 */
144function isValidQQ(str) {
145 const qqRegex = /^\d{5,13}$/; // QQ号为5-13位数字
146 return qqRegex.test(str);
147}
148
149/**
150 * 验证是否为有效的MD5(32位十六进制)
151 * @param {string} str 待验证字符串
152 * @returns {boolean}
153 */
154function isValidMD5(str) {
155 const md5Regex = /^[0-9a-fA-F]{32}$/;
156 return md5Regex.test(str);
157}
158
159/**
160 * 计算字符串的MD5哈希(UTF-8编码)
161 * @param {string} str 输入字符串
162 * @returns {Promise<string>} 32位小写MD5
163 */
164async function md5(str) {
165 const encoder = new TextEncoder();
166 const data = encoder.encode(str);
167 const digest = await crypto.subtle.digest('MD5', data);
168 return Array.from(new Uint8Array(digest))
169 .map(byte => byte.toString(16).padStart(2, '0'))
170 .join('');
171}