匹配范围
*://*.gitee.com/**://so.gitee.com/*gitee-repo-search用途 在 Gitee 上按关键词搜索公开仓库,返回仓库名称、所属用户或组织、语言、星标数、Fork 数、简介、最近推送时间和仓库链接。 执行前提 请在 gitee.com 或 so.gitee.com 页面执行。脚本使用搜索页公开 JSON 路径读取结果,不需要登录。 如果当前浏览器页面被 so.gitee.com 的安全验证码拦截,页面内容可能无法正常读…
*://*.gitee.com/**://so.gitee.com/*dom.read1return await (async () => {2 const input = params && typeof params === "object" ? params : {};3 const rawQuery = input.query ?? input.keyword;4 const query = typeof rawQuery === "string" ? rawQuery.trim() : String(rawQuery ?? "").trim();5 6 if (!query) {7 throw new Error("参数 query 必填,也兼容 keyword。");8 }9 10 const rawLimit = Number(input.limit ?? 10);11 const limit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), 50) : 10;12 const endpoint = new URL("https://api.indexea.com/v1/search/widget/wong1slagnlmzwvsu5ya");13 endpoint.searchParams.set("q", query);14 endpoint.searchParams.set("from", "0");15 endpoint.searchParams.set("size", String(limit));16 17 const response = await fetch(endpoint.toString(), {18 method: "GET",19 headers: { Accept: "application/json" },20 credentials: "omit",21 });22 23 if (!response.ok) {24 throw new Error(`Gitee 搜索接口请求失败:${response.status} ${response.statusText}。如当前页面处于安全验证码或风控策略下,请先用浏览器内执行验证确认接口可读。`);25 }26 27 const payload = await response.json();28 const hits = Array.isArray(payload?.hits?.hits) ? payload.hits.hits : [];29 30 const toList = (value) => {31 if (Array.isArray(value)) return value.map((item) => String(item ?? "").trim()).filter(Boolean);32 if (value === undefined || value === null) return [];33 const text = String(value).trim();34 return text ? [text] : [];35 };36 37 const firstText = (value) => toList(value)[0] ?? "";38 const firstNumber = (value) => {39 const text = firstText(value);40 if (!text) return 0;41 const number = Number(text);42 return Number.isFinite(number) ? number : 0;43 };44 45 const parseRepoUrl = (value) => {46 const text = firstText(value);47 if (!text) return null;48 49 try {50 const url = new URL(text);51 if (url.hostname !== "gitee.com") return null;52 53 const parts = url.pathname.split("/").map((part) => part.trim()).filter(Boolean);54 if (parts.length !== 2) return null;55 56 const owner = decodeURIComponent(parts[0]);57 const repo = decodeURIComponent(parts[1]).replace(/\.git$/i, "");58 if (!owner || !repo) return null;59 60 return {61 owner,62 repo,63 url: `https://gitee.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`,64 };65 } catch {66 return null;67 }68 };69 70 return hits71 .map((hit) => hit?.fields ?? {})72 .map((fields) => {73 const repoUrl = parseRepoUrl(fields.url);74 if (!repoUrl) return null;75 76 return {77 name: firstText(fields.title) || `${repoUrl.owner}/${repoUrl.repo}`,78 owner: repoUrl.owner,79 repo: repoUrl.repo,80 language: toList(fields.langs).join(", "),81 stars: firstNumber(fields["count.star"]),82 forks: firstNumber(fields["count.fork"] ?? fields.fork),83 description: firstText(fields.description),84 last_push_at: firstText(fields.last_push_at),85 url: repoUrl.url,86 };87 })88 .filter(Boolean)89 .map((item, index) => ({90 rank: index + 1,91 ...item,92 }));93})();