Files
GithubStatsManager-Android/services/github.js
2026-04-27 23:58:12 +08:00

155 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Constants from 'expo-constants';
const GITHUB_API_URL = 'https://api.github.com';
// 用于检查更新的仓库地址,从 app.json extra 中读取
const APP_REPO = Constants.expoConfig?.extra?.appRepo || 'EchoZenith/GithubStarsManager-Android';
// 当前版本号从 app.json 中读取version 字段)
const CURRENT_VERSION = Constants.expoConfig?.version || '1.0.0';
// 自定义错误类型Token 过期或无效时抛出,供上层 UI 捕获后跳转 Token 输入页
class TokenExpiredError extends Error {
constructor(message) {
super(message);
this.name = 'TokenExpiredError';
}
}
export { TokenExpiredError };
// 构建 GitHub API 请求头
function buildHeaders(token) {
return {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github.v3+json',
};
}
// 分页获取用户所有星标仓库(每页 100 条,自动翻页直到取完)
export async function fetchStarredRepos(token) {
if (!token) {
throw new TokenExpiredError('请先输入 GitHub Token');
}
let allRepos = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`${GITHUB_API_URL}/user/starred?per_page=100&page=${page}`,
{
method: 'GET',
headers: buildHeaders(token),
}
);
if (response.status === 401) {
throw new TokenExpiredError('GitHub Token 已过期或无效,请重新输入');
}
if (!response.ok) {
throw new Error(`GitHub API 错误!状态码: ${response.status}`);
}
const data = await response.json();
if (data.length === 0) {
hasMore = false;
} else {
allRepos = allRepos.concat(data);
page++;
}
}
return allRepos;
}
// 获取仓库的 README 原始内容raw 格式直接返回 markdown 文本)
export async function fetchReadme(token, fullName) {
if (!token) {
throw new TokenExpiredError('请先输入 GitHub Token');
}
const response = await fetch(
`${GITHUB_API_URL}/repos/${fullName}/readme`,
{
method: 'GET',
headers: {
...buildHeaders(token),
Accept: 'application/vnd.github.raw',
},
}
);
if (response.status === 401) {
throw new TokenExpiredError('GitHub Token 已过期或无效,请重新输入');
}
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error(`获取 README 失败!状态码: ${response.status}`);
}
return await response.text();
}
// 检查应用更新:对比当前版本与 GitHub Releases 最新版本号
export async function checkUpdate(token) {
try {
const headers = {
Accept: 'application/vnd.github.v3+json',
};
if (token) {
headers.Authorization = `Bearer ${token}`;
}
const response = await fetch(
`${GITHUB_API_URL}/repos/${APP_REPO}/releases/latest`,
{
method: 'GET',
headers,
}
);
if (response.status === 404) {
return { hasUpdate: false, error: null, message: '未找到发布版本' };
}
if (!response.ok) {
return { hasUpdate: false, error: '检查更新失败', message: null };
}
const data = await response.json();
const latestVersion = data.tag_name.replace(/^v/, '');
const currentParts = CURRENT_VERSION.split('.').map(Number);
const latestParts = latestVersion.split('.').map(Number);
// 逐段比较版本号major.minor.patch
let hasUpdate = false;
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
const cur = currentParts[i] || 0;
const lat = latestParts[i] || 0;
if (lat > cur) { hasUpdate = true; break; }
if (lat < cur) break;
}
return {
hasUpdate,
currentVersion: CURRENT_VERSION,
latestVersion,
releaseUrl: data.html_url,
releaseName: data.name || data.tag_name,
releaseBody: data.body ? data.body.split('\n').slice(0, 5).join('\n') : '',
publishedAt: data.published_at,
error: null,
message: hasUpdate
? `发现新版本 v${latestVersion}`
: '已是最新版本',
};
} catch (e) {
return { hasUpdate: false, error: e.message || '网络错误', message: null };
}
}