Initialize Clash Web source and deployment documentation

This commit is contained in:
lhc committed 2026-10-05 17:32:00 +08:00
commit 8e0fa00a73
66 files changed
+8532

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
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,
})
}