// 腾讯EdgeOne边缘函数:支持Gravatar(邮箱/MD5)和QQ头像(QQ号)代理,全程UTF-8编码 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) /** * 处理请求并代理返回对应的头像(Gravatar或QQ头像) * @param {Request} request */ async function handleRequest(request) { try { const url = new URL(request.url) const pathParts = url.pathname.split('/').filter(part => part) // 路径格式校验:必须为/avatar/[参数] if (pathParts.length !== 2 || pathParts[0] !== 'avatar') { return new Response( '请使用正确的路径格式:\n' + '/avatar/QQ号(如/avatar/123456)\n' + '/avatar/邮箱(如/avatar/user@example.com)\n' + '/avatar/MD5(如/avatar/md5hashvalue)', { status: 400, headers: { 'Content-Type': 'text/plain; charset=utf-8' } } ) } const param = pathParts[1] if (!param) { return new Response( '请提供参数(QQ号/邮箱/MD5),例如:\n' + '/avatar/123456(QQ号)\n' + '/avatar/user@example.com(邮箱)', { status: 400, headers: { 'Content-Type': 'text/plain; charset=utf-8' } } ) } // 1. 先判断是否为QQ号(5-13位数字) if (isValidQQ(param)) { return handleQQAvatar(param, url.searchParams) } // 2. 非QQ号则走原有Gravatar逻辑 let md5Hash; if (isValidMD5(param)) { md5Hash = param.toLowerCase(); // MD5统一转小写 } else { // 视为邮箱处理 const processedEmail = param.trim().toLowerCase(); md5Hash = await md5(processedEmail); } // 构建Gravatar URL const gravatarUrl = `https://www.gravatar.com/avatar/${md5Hash}${url.search}` return fetchAndProxy(gravatarUrl) } catch (error) { return new Response( `处理请求时出错: ${error.message}`, { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8' } } ) } } /** * 处理QQ头像代理 * @param {string} qqNumber QQ号 * @param {URLSearchParams} searchParams 查询参数(如size) */ async function handleQQAvatar(qqNumber, searchParams) { // QQ头像官方接口:https://q.qlogo.cn/g?b=qq&nk=QQ号&s=尺寸 // 映射参数:将用户传入的size转换为s(默认尺寸100) const size = searchParams.get('size') || '100'; // 构建QQ头像URL(保留其他可能的参数,如自定义头像参数) const qqAvatarUrl = new URL('https://q.qlogo.cn/g'); qqAvatarUrl.searchParams.set('b', 'qq'); // 固定为qq标识 qqAvatarUrl.searchParams.set('nk', qqNumber); // QQ号 qqAvatarUrl.searchParams.set('s', size); // 尺寸 // 透传其他参数(如用户可能传入的其他自定义参数) searchParams.forEach((value, key) => { if (!['size', 'b', 'nk', 's'].includes(key)) { qqAvatarUrl.searchParams.set(key, value); } }); return fetchAndProxy(qqAvatarUrl.toString()) } /** * 通用代理请求逻辑(复用Gravatar和QQ头像的响应处理) * @param {string} targetUrl 目标头像URL */ async function fetchAndProxy(targetUrl) { const response = await fetch(targetUrl, { method: 'GET', headers: { 'User-Agent': 'EdgeOne Avatar Proxy', 'Accept': 'image/*' }, cache: 'default' }); if (!response.ok) { return new Response( `获取头像失败: ${response.statusText}`, { status: response.status, headers: { 'Content-Type': 'text/plain; charset=utf-8' } } ); } // 处理响应头(统一设置CORS、删除敏感头) const headers = new Headers(response.headers); headers.delete('access-control-allow-origin'); headers.delete('set-cookie'); headers.set('Access-Control-Allow-Origin', '*'); headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS'); // 文本类型响应补全UTF-8编码 const contentType = headers.get('Content-Type'); if (contentType && contentType.startsWith('text/')) { headers.set('Content-Type', `${contentType}; charset=utf-8`); } return new Response(response.body, { status: response.status, headers }); } /** * 验证是否为有效的QQ号(5-13位数字) * @param {string} str 待验证字符串 * @returns {boolean} */ function isValidQQ(str) { const qqRegex = /^\d{5,13}$/; // QQ号为5-13位数字 return qqRegex.test(str); } /** * 验证是否为有效的MD5(32位十六进制) * @param {string} str 待验证字符串 * @returns {boolean} */ function isValidMD5(str) { const md5Regex = /^[0-9a-fA-F]{32}$/; return md5Regex.test(str); } /** * 计算字符串的MD5哈希(UTF-8编码) * @param {string} str 输入字符串 * @returns {Promise} 32位小写MD5 */ async function md5(str) { const encoder = new TextEncoder(); const data = encoder.encode(str); const digest = await crypto.subtle.digest('MD5', data); return Array.from(new Uint8Array(digest)) .map(byte => byte.toString(16).padStart(2, '0')) .join(''); }