155 lines
4.1 KiB
JavaScript
155 lines
4.1 KiB
JavaScript
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 };
|
||
}
|
||
}
|