Files

126 lines
3.6 KiB
Go
Raw Permalink 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.
package app
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)
const coreReleaseEndpoint = "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest"
var stableVersionPattern = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)$`)
type coreRelease struct {
Tag string `json:"tag_name"`
Published string `json:"published_at"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
}
func stableVersion(version string) ([3]int, bool) {
var parts [3]int
match := stableVersionPattern.FindStringSubmatch(strings.TrimSpace(version))
if len(match) != 4 {
return parts, false
}
for i := range parts {
n, err := strconv.Atoi(match[i+1])
if err != nil {
return parts, false
}
parts[i] = n
}
return parts, true
}
func newerStableVersion(current, latest string) *bool {
a, currentOK := stableVersion(current)
b, latestOK := stableVersion(latest)
if !currentOK || !latestOK {
return nil
}
newer := false
for i := range a {
if b[i] != a[i] {
newer = b[i] > a[i]
break
}
}
return &newer
}
func fetchCoreRelease(ctx context.Context, client *http.Client) (*coreRelease, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, coreReleaseEndpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", "ClashWeb-update-check")
resp, err := client.Do(req)
if err != nil {
return nil, errors.New("无法连接 GitHub,请检查网络后重试")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusTooManyRequests {
return nil, errors.New("GitHub 更新检查暂时受限,请稍后重试")
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("更新检查失败(HTTP %d)", resp.StatusCode)
}
data, err := io.ReadAll(io.LimitReader(resp.Body, (2<<20)+1))
if err != nil || len(data) > 2<<20 {
return nil, errors.New("版本信息读取失败")
}
var release coreRelease
if err := json.Unmarshal(data, &release); err != nil {
return nil, errors.New("版本信息格式错误")
}
if _, ok := stableVersion(release.Tag); !ok || release.Draft || release.Prerelease {
return nil, errors.New("未获取到有效的 Mihomo 稳定版本")
}
return &release, nil
}
func (a *App) latestCoreRelease(ctx context.Context) (*coreRelease, time.Time, error) {
a.updatesMu.Lock()
defer a.updatesMu.Unlock()
if a.updateCache != nil && time.Since(a.updateTime) < 10*time.Minute {
return a.updateCache, a.updateTime, nil
}
release, err := fetchCoreRelease(ctx, a.download)
if err != nil {
return nil, time.Time{}, err
}
a.updateCache, a.updateTime = release, time.Now()
return release, a.updateTime, nil
}
func (a *App) coreUpdates(w http.ResponseWriter, r *http.Request) {
release, checked, err := a.latestCoreRelease(r.Context())
if err != nil {
writeError(w, http.StatusBadGateway, err.Error())
return
}
var version struct {
Version string `json:"version"`
}
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
_ = a.core.Request(ctx, http.MethodGet, "/version", nil, &version)
jsonResponse(w, http.StatusOK, map[string]any{
"currentVersion": version.Version, "latestVersion": release.Tag,
"updateAvailable": newerStableVersion(version.Version, release.Tag),
"checkedAt": checked.Format(time.RFC3339), "publishedAt": release.Published,
"releaseURL": "https://github.com/MetaCubeX/mihomo/releases/tag/" + release.Tag,
})
}