mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-22 15:03:35 +08:00
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package pluginstore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/httpfetch"
|
|
)
|
|
|
|
const userAgent = "CLIProxyAPI"
|
|
|
|
// HTTPDoer abstracts the HTTP client used to execute requests.
|
|
type HTTPDoer = httpfetch.Doer
|
|
|
|
type Client struct {
|
|
HTTPClient HTTPDoer
|
|
RegistryURL string
|
|
UserAgent string
|
|
}
|
|
|
|
type Release struct {
|
|
TagName string `json:"tag_name"`
|
|
Assets []ReleaseAsset `json:"assets"`
|
|
}
|
|
|
|
type ReleaseAsset struct {
|
|
Name string `json:"name"`
|
|
BrowserDownloadURL string `json:"browser_download_url"`
|
|
}
|
|
|
|
func (c Client) FetchRegistry(ctx context.Context) (Registry, error) {
|
|
registryURL := strings.TrimSpace(c.RegistryURL)
|
|
if registryURL == "" {
|
|
registryURL = DefaultRegistryURL
|
|
}
|
|
data, errDownload := c.get(ctx, registryURL, "application/json")
|
|
if errDownload != nil {
|
|
return Registry{}, errDownload
|
|
}
|
|
registry, errParse := ParseRegistry(data)
|
|
if errParse != nil {
|
|
return Registry{}, errParse
|
|
}
|
|
return registry, nil
|
|
}
|
|
|
|
func (c Client) FetchRelease(ctx context.Context, plugin Plugin) (Release, error) {
|
|
owner, repo, errRepository := GitHubRepositoryParts(plugin.Repository)
|
|
if errRepository != nil {
|
|
return Release{}, errRepository
|
|
}
|
|
releaseURL := fmt.Sprintf(
|
|
"https://api.github.com/repos/%s/%s/releases/tags/%s",
|
|
url.PathEscape(owner),
|
|
url.PathEscape(repo),
|
|
url.PathEscape("v"+strings.TrimSpace(plugin.Version)),
|
|
)
|
|
data, errDownload := c.get(ctx, releaseURL, "application/vnd.github+json")
|
|
if errDownload != nil {
|
|
return Release{}, errDownload
|
|
}
|
|
var release Release
|
|
if errDecode := json.Unmarshal(data, &release); errDecode != nil {
|
|
return Release{}, fmt.Errorf("decode release: %w", errDecode)
|
|
}
|
|
return release, nil
|
|
}
|
|
|
|
func (c Client) DownloadAsset(ctx context.Context, asset ReleaseAsset) ([]byte, error) {
|
|
if strings.TrimSpace(asset.BrowserDownloadURL) == "" {
|
|
return nil, fmt.Errorf("asset %q missing browser_download_url", asset.Name)
|
|
}
|
|
return c.get(ctx, asset.BrowserDownloadURL, "application/octet-stream")
|
|
}
|
|
|
|
func (c Client) get(ctx context.Context, requestURL string, accept string) ([]byte, error) {
|
|
return httpfetch.GetBytes(ctx, c.httpClient(), requestURL, map[string]string{
|
|
"Accept": accept,
|
|
"User-Agent": c.userAgent(),
|
|
}, 0)
|
|
}
|
|
|
|
func (c Client) httpClient() HTTPDoer {
|
|
if c.HTTPClient != nil {
|
|
return c.HTTPClient
|
|
}
|
|
return http.DefaultClient
|
|
}
|
|
|
|
func (c Client) userAgent() string {
|
|
if strings.TrimSpace(c.UserAgent) != "" {
|
|
return strings.TrimSpace(c.UserAgent)
|
|
}
|
|
return userAgent
|
|
}
|
|
|
|
func SelectReleaseAssets(release Release, id, version, goos, goarch string) (ReleaseAsset, ReleaseAsset, error) {
|
|
archiveName := ArchiveName(id, version, goos, goarch)
|
|
var archiveAsset ReleaseAsset
|
|
var checksumAsset ReleaseAsset
|
|
for _, asset := range release.Assets {
|
|
switch strings.TrimSpace(asset.Name) {
|
|
case archiveName:
|
|
archiveAsset = asset
|
|
case "checksums.txt":
|
|
checksumAsset = asset
|
|
}
|
|
}
|
|
if strings.TrimSpace(archiveAsset.Name) == "" {
|
|
return ReleaseAsset{}, ReleaseAsset{}, fmt.Errorf("release asset %s not found", archiveName)
|
|
}
|
|
if strings.TrimSpace(checksumAsset.Name) == "" {
|
|
return ReleaseAsset{}, ReleaseAsset{}, fmt.Errorf("release asset checksums.txt not found")
|
|
}
|
|
return archiveAsset, checksumAsset, nil
|
|
}
|
|
|
|
func ArchiveName(id, version, goos, goarch string) string {
|
|
return fmt.Sprintf(
|
|
"%s_%s_%s_%s.zip",
|
|
strings.TrimSpace(id),
|
|
strings.TrimSpace(version),
|
|
strings.TrimSpace(goos),
|
|
strings.TrimSpace(goarch),
|
|
)
|
|
}
|