Последняя активность 1 month ago

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

qxzhan's Avatar qxzhan ревизий этого фрагмента 1 month ago. К ревизии

Без изменений

qxzhan's Avatar qxzhan ревизий этого фрагмента 1 month ago. К ревизии

1 file changed, 171 insertions

js(файл создан)

@@ -0,0 +1,171 @@
1 + // 腾讯EdgeOne边缘函数:支持Gravatar(邮箱/MD5)和QQ头像(QQ号)代理,全程UTF-8编码
2 + addEventListener('fetch', event => {
3 + event.respondWith(handleRequest(event.request))
4 + })
5 +
6 + /**
7 + * 处理请求并代理返回对应的头像(Gravatar或QQ头像)
8 + * @param {Request} request
9 + */
10 + async 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 + */
77 + async 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 + */
100 + async 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 + */
144 + function 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 + */
154 + function 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 + */
164 + async 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 + }
Новее Позже