Merge pull request #307 from Mystically11/add-prereleases-to-releases
feat: Add option to include GitHub repository pre-releases in releases widget
This commit is contained in:
commit
ec9d455d18
2 changed files with 112 additions and 69 deletions
|
@ -1332,6 +1332,17 @@ repositories:
|
|||
- dockerhub:nginx:stable-alpine
|
||||
```
|
||||
|
||||
To include prereleases you can specify the repository as an object and use the `include-prereleases` property:
|
||||
|
||||
**Note: This feature is currently only available for GitHub repositories.**
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
- gitlab:inkscape/inkscape
|
||||
- repository: glanceapp/glance
|
||||
include-prereleases: true
|
||||
- codeberg:redict/redict
|
||||
```
|
||||
|
||||
##### `show-source-icon`
|
||||
Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
|
||||
|
|
|
@ -11,20 +11,21 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var releasesWidgetTemplate = mustParseTemplate("releases.html", "widget-base.html")
|
||||
|
||||
type releasesWidget struct {
|
||||
widgetBase `yaml:",inline"`
|
||||
Releases appReleaseList `yaml:"-"`
|
||||
releaseRequests []*releaseRequest `yaml:"-"`
|
||||
Repositories []string `yaml:"repositories"`
|
||||
Token string `yaml:"token"`
|
||||
GitLabToken string `yaml:"gitlab-token"`
|
||||
Limit int `yaml:"limit"`
|
||||
CollapseAfter int `yaml:"collapse-after"`
|
||||
ShowSourceIcon bool `yaml:"show-source-icon"`
|
||||
widgetBase `yaml:",inline"`
|
||||
Releases appReleaseList `yaml:"-"`
|
||||
Repositories []*releaseRequest `yaml:"repositories"`
|
||||
Token string `yaml:"token"`
|
||||
GitLabToken string `yaml:"gitlab-token"`
|
||||
Limit int `yaml:"limit"`
|
||||
CollapseAfter int `yaml:"collapse-after"`
|
||||
ShowSourceIcon bool `yaml:"show-source-icon"`
|
||||
}
|
||||
|
||||
func (widget *releasesWidget) initialize() error {
|
||||
|
@ -38,51 +39,21 @@ func (widget *releasesWidget) initialize() error {
|
|||
widget.CollapseAfter = 5
|
||||
}
|
||||
|
||||
for _, repository := range widget.Repositories {
|
||||
parts := strings.SplitN(repository, ":", 2)
|
||||
var request *releaseRequest
|
||||
if len(parts) == 1 {
|
||||
request = &releaseRequest{
|
||||
source: releaseSourceGithub,
|
||||
repository: repository,
|
||||
}
|
||||
for i := range widget.Repositories {
|
||||
r := widget.Repositories[i]
|
||||
|
||||
if widget.Token != "" {
|
||||
request.token = &widget.Token
|
||||
}
|
||||
} else if len(parts) == 2 {
|
||||
if parts[0] == string(releaseSourceGitlab) {
|
||||
request = &releaseRequest{
|
||||
source: releaseSourceGitlab,
|
||||
repository: parts[1],
|
||||
}
|
||||
|
||||
if widget.GitLabToken != "" {
|
||||
request.token = &widget.GitLabToken
|
||||
}
|
||||
} else if parts[0] == string(releaseSourceDockerHub) {
|
||||
request = &releaseRequest{
|
||||
source: releaseSourceDockerHub,
|
||||
repository: parts[1],
|
||||
}
|
||||
} else if parts[0] == string(releaseSourceCodeberg) {
|
||||
request = &releaseRequest{
|
||||
source: releaseSourceCodeberg,
|
||||
repository: parts[1],
|
||||
}
|
||||
} else {
|
||||
return errors.New("invalid repository source " + parts[0])
|
||||
}
|
||||
if r.source == releaseSourceGithub && widget.Token != "" {
|
||||
r.token = &widget.Token
|
||||
} else if r.source == releaseSourceGitlab && widget.GitLabToken != "" {
|
||||
r.token = &widget.GitLabToken
|
||||
}
|
||||
|
||||
widget.releaseRequests = append(widget.releaseRequests, request)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (widget *releasesWidget) update(ctx context.Context) {
|
||||
releases, err := fetchLatestReleases(widget.releaseRequests)
|
||||
releases, err := fetchLatestReleases(widget.Repositories)
|
||||
|
||||
if !widget.canContinueUpdateAfterHandlingErr(err) {
|
||||
return
|
||||
|
@ -133,9 +104,53 @@ func (r appReleaseList) sortByNewest() appReleaseList {
|
|||
}
|
||||
|
||||
type releaseRequest struct {
|
||||
source releaseSource
|
||||
repository string
|
||||
token *string
|
||||
IncludePreleases bool `yaml:"include-prereleases"`
|
||||
Repository string `yaml:"repository"`
|
||||
|
||||
source releaseSource
|
||||
token *string
|
||||
}
|
||||
|
||||
func (r *releaseRequest) UnmarshalYAML(node *yaml.Node) error {
|
||||
type releaseRequestAlias releaseRequest
|
||||
alias := (*releaseRequestAlias)(r)
|
||||
var repository string
|
||||
|
||||
if err := node.Decode(&repository); err != nil {
|
||||
if err := node.Decode(alias); err != nil {
|
||||
return fmt.Errorf("could not umarshal repository into string or struct: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Repository == "" {
|
||||
if repository == "" {
|
||||
return errors.New("repository is required")
|
||||
} else {
|
||||
r.Repository = repository
|
||||
}
|
||||
}
|
||||
|
||||
parts := strings.SplitN(repository, ":", 2)
|
||||
if len(parts) == 1 {
|
||||
r.source = releaseSourceGithub
|
||||
} else if len(parts) == 2 {
|
||||
r.Repository = parts[1]
|
||||
|
||||
switch parts[0] {
|
||||
case string(releaseSourceGithub):
|
||||
r.source = releaseSourceGithub
|
||||
case string(releaseSourceGitlab):
|
||||
r.source = releaseSourceGitlab
|
||||
case string(releaseSourceDockerHub):
|
||||
r.source = releaseSourceDockerHub
|
||||
case string(releaseSourceCodeberg):
|
||||
r.source = releaseSourceCodeberg
|
||||
default:
|
||||
return errors.New("invalid source")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
|
||||
|
@ -152,7 +167,7 @@ func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
|
|||
for i := range results {
|
||||
if errs[i] != nil {
|
||||
failed++
|
||||
slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].repository, "error", errs[i])
|
||||
slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].Repository, "error", errs[i])
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -187,7 +202,7 @@ func fetchLatestReleaseTask(request *releaseRequest) (*appRelease, error) {
|
|||
return nil, errors.New("unsupported source")
|
||||
}
|
||||
|
||||
type githubReleaseLatestResponseJson struct {
|
||||
type githubReleaseResponseJson struct {
|
||||
TagName string `json:"tag_name"`
|
||||
PublishedAt string `json:"published_at"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
|
@ -197,12 +212,16 @@ type githubReleaseLatestResponseJson struct {
|
|||
}
|
||||
|
||||
func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
|
||||
httpRequest, err := http.NewRequest(
|
||||
"GET",
|
||||
fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.repository),
|
||||
nil,
|
||||
)
|
||||
var requestURL string
|
||||
if !request.IncludePreleases {
|
||||
requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.Repository)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases", request.Repository)
|
||||
}
|
||||
|
||||
fmt.Println(requestURL)
|
||||
|
||||
httpRequest, err := http.NewRequest("GET", requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -211,14 +230,29 @@ func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
|
|||
httpRequest.Header.Add("Authorization", "Bearer "+(*request.token))
|
||||
}
|
||||
|
||||
response, err := decodeJsonFromRequest[githubReleaseLatestResponseJson](defaultHTTPClient, httpRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var response githubReleaseResponseJson
|
||||
|
||||
if !request.IncludePreleases {
|
||||
response, err = decodeJsonFromRequest[githubReleaseResponseJson](defaultHTTPClient, httpRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
responses, err := decodeJsonFromRequest[[]githubReleaseResponseJson](defaultHTTPClient, httpRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(responses) == 0 {
|
||||
return nil, fmt.Errorf("no releases found for repository %s", request.Repository)
|
||||
}
|
||||
|
||||
response = responses[0]
|
||||
}
|
||||
|
||||
return &appRelease{
|
||||
Source: releaseSourceGithub,
|
||||
Name: request.repository,
|
||||
Name: request.Repository,
|
||||
Version: normalizeVersionFormat(response.TagName),
|
||||
NotesUrl: response.HtmlUrl,
|
||||
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
||||
|
@ -241,17 +275,15 @@ const dockerHubTagsURLFormat = "https://hub.docker.com/v2/namespaces/%s/reposito
|
|||
const dockerHubSpecificTagURLFormat = "https://hub.docker.com/v2/namespaces/%s/repositories/%s/tags/%s"
|
||||
|
||||
func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
|
||||
|
||||
nameParts := strings.Split(request.repository, "/")
|
||||
nameParts := strings.Split(request.Repository, "/")
|
||||
|
||||
if len(nameParts) > 2 {
|
||||
return nil, fmt.Errorf("invalid repository name: %s", request.repository)
|
||||
return nil, fmt.Errorf("invalid repository name: %s", request.Repository)
|
||||
} else if len(nameParts) == 1 {
|
||||
nameParts = []string{"library", nameParts[0]}
|
||||
}
|
||||
|
||||
tagParts := strings.SplitN(nameParts[1], ":", 2)
|
||||
|
||||
var requestURL string
|
||||
|
||||
if len(tagParts) == 2 {
|
||||
|
@ -278,7 +310,7 @@ func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
|
|||
}
|
||||
|
||||
if len(response.Results) == 0 {
|
||||
return nil, fmt.Errorf("no tags found for repository: %s", request.repository)
|
||||
return nil, fmt.Errorf("no tags found for repository: %s", request.Repository)
|
||||
}
|
||||
|
||||
tag = &response.Results[0]
|
||||
|
@ -331,7 +363,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
|
|||
"GET",
|
||||
fmt.Sprintf(
|
||||
"https://gitlab.com/api/v4/projects/%s/releases/permalink/latest",
|
||||
url.QueryEscape(request.repository),
|
||||
url.QueryEscape(request.Repository),
|
||||
),
|
||||
nil,
|
||||
)
|
||||
|
@ -350,7 +382,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
|
|||
|
||||
return &appRelease{
|
||||
Source: releaseSourceGitlab,
|
||||
Name: request.repository,
|
||||
Name: request.Repository,
|
||||
Version: normalizeVersionFormat(response.TagName),
|
||||
NotesUrl: response.Links.Self,
|
||||
TimeReleased: parseRFC3339Time(response.ReleasedAt),
|
||||
|
@ -368,7 +400,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
|
|||
"GET",
|
||||
fmt.Sprintf(
|
||||
"https://codeberg.org/api/v1/repos/%s/releases/latest",
|
||||
request.repository,
|
||||
request.Repository,
|
||||
),
|
||||
nil,
|
||||
)
|
||||
|
@ -383,7 +415,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
|
|||
|
||||
return &appRelease{
|
||||
Source: releaseSourceCodeberg,
|
||||
Name: request.repository,
|
||||
Name: request.Repository,
|
||||
Version: normalizeVersionFormat(response.TagName),
|
||||
NotesUrl: response.HtmlUrl,
|
||||
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
||||
|
|
Loading…
Add table
Reference in a new issue