瀏覽代碼

Merge branch 'glanceapp:main' into crypto

Chandler 10 月之前
父節點
當前提交
662dbb9f3a

+ 1 - 0
.github/FUNDING.yml

@@ -0,0 +1 @@
+github: [glanceapp]

+ 39 - 0
.github/workflows/release.yaml

@@ -0,0 +1,39 @@
+name: Create release
+
+permissions:
+  contents: write
+
+on:
+  push:
+    tags:
+      - 'v*'
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout the target Git reference
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Log in to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Set up Golang
+        uses: actions/setup-go@v5
+        with:
+          go-version-file: go.mod
+
+      - name: Set up Docker buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v5
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          args: release

+ 72 - 0
.goreleaser.yaml

@@ -0,0 +1,72 @@
+project_name: glanceapp/glance
+
+checksum:
+  disable: true
+
+builds:
+  - binary: glance
+    env:
+      - CGO_ENABLED=0
+    goos:
+      - linux
+      - openbsd
+      - freebsd
+      - windows
+      - darwin
+    goarch:
+      - amd64
+      - arm64
+      - arm
+      - 386
+    goarm:
+      - 7
+    ldflags:
+      - -s -w -X github.com/glanceapp/glance/internal/glance.buildVersion={{ .Tag }}
+
+archives:
+  -
+    name_template: "glance-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}"
+    files:
+      - nothing*
+    format_overrides:
+      - goos: windows
+        format: zip
+
+dockers:
+  - image_templates:
+      - &amd64_image "{{ .ProjectName }}:{{ .Tag }}-amd64"
+    build_flag_templates:
+      - --platform=linux/amd64
+    goarch: amd64
+    use: buildx
+    dockerfile: Dockerfile.goreleaser
+
+  - image_templates:
+      - &arm64v8_image "{{ .ProjectName }}:{{ .Tag }}-arm64"
+    build_flag_templates:
+      - --platform=linux/arm64
+    goarch: arm64
+    use: buildx
+    dockerfile: Dockerfile.goreleaser
+
+  - image_templates:
+      - &arm64v7_image "{{ .ProjectName }}:{{ .Tag }}-arm64v7"
+    build_flag_templates:
+      - --platform=linux/arm64/v7
+    goarch: arm
+    goarm: 7
+    use: buildx
+    dockerfile: Dockerfile.goreleaser
+
+docker_manifests:
+  - name_template: "{{ .ProjectName }}:{{ .Tag }}"
+    image_templates:
+      - *amd64_image
+      - *arm64v8_image
+      - *arm64v7_image
+  - name_template: "{{ .ProjectName }}:latest"
+    skip_push: auto
+    image_templates:
+      - *amd64_image
+      - *arm64v8_image
+      - *arm64v7_image

+ 7 - 5
Dockerfile

@@ -1,11 +1,13 @@
-FROM alpine:3.20
+FROM golang:1.22.5-alpine3.20 AS builder
+
+WORKDIR /app
+COPY . /app
+RUN CGO_ENABLED=0 go build .
 
-ARG TARGETOS
-ARG TARGETARCH
-ARG TARGETVARIANT
+FROM alpine:3.20
 
 WORKDIR /app
-COPY build/glance-$TARGETOS-$TARGETARCH${TARGETVARIANT} /app/glance
+COPY --from=builder /app/glance .
 
 EXPOSE 8080/tcp
 ENTRYPOINT ["/app/glance"]

+ 8 - 0
Dockerfile.goreleaser

@@ -0,0 +1,8 @@
+FROM alpine:3.20
+
+WORKDIR /app
+COPY glance .
+
+EXPOSE 8080/tcp
+
+ENTRYPOINT ["/app/glance"]

+ 0 - 14
Dockerfile.single-platform

@@ -1,14 +0,0 @@
-FROM golang:1.22.3-alpine3.19 AS builder
-
-WORKDIR /app
-COPY . /app
-RUN CGO_ENABLED=0 go build .
-
-
-FROM alpine:3.19
-
-WORKDIR /app
-COPY --from=builder /app/glance .
-
-EXPOSE 8080/tcp
-ENTRYPOINT ["/app/glance"]

+ 1 - 1
README.md

@@ -102,7 +102,7 @@ Build the image:
 **Make sure to replace "owner" with your name or organization.**
 
 ```bash
-docker build -t owner/glance:latest -f Dockerfile.single-platform .
+docker build -t owner/glance:latest .
 ```
 
 Push the image to your registry:

+ 0 - 1
docs/configuration.md

@@ -40,7 +40,6 @@ pages:
     columns:
       - size: small
         widgets:
-          - type: clock
           - type: calendar
 
           - type: rss

二進制
docs/images/themes/kanagawa-dark.png


+ 10 - 0
docs/themes.md

@@ -53,6 +53,16 @@ theme:
   primary-color: 97 13 80
 ```
 
+### Kanagawa Dark
+![screenshot](images/themes/kanagawa-dark.png)
+```yaml
+theme:
+  background-color: 240 13 14
+  primary-color: 51 33 68
+  negative-color: 358 100 68
+  contrast-multiplier: 1.2
+```
+
 ### Tucan
 ![screenshot](images/themes/tucan.png)
 ```yaml

+ 1 - 0
internal/assets/static/main.css

@@ -731,6 +731,7 @@ kbd:active {
     height: 6rem;
     font: inherit;
     outline: none;
+    color: var(--color-text-highlight);
 }
 
 .search-input::placeholder {

+ 4 - 23
internal/feed/github.go

@@ -8,12 +8,10 @@ import (
 	"time"
 )
 
-type githubReleaseResponseJson struct {
+type githubReleaseLatestResponseJson struct {
 	TagName     string `json:"tag_name"`
 	PublishedAt string `json:"published_at"`
 	HtmlUrl     string `json:"html_url"`
-	Draft       bool   `json:"draft"`
-	PreRelease  bool   `json:"prerelease"`
 	Reactions   struct {
 		Downvotes int `json:"-1"`
 	} `json:"reactions"`
@@ -39,7 +37,7 @@ func FetchLatestReleasesFromGithub(repositories []string, token string) (AppRele
 	requests := make([]*http.Request, len(repositories))
 
 	for i, repository := range repositories {
-		request, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s/releases?per_page=10", repository), nil)
+		request, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repository), nil)
 
 		if token != "" {
 			request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
@@ -48,7 +46,7 @@ func FetchLatestReleasesFromGithub(repositories []string, token string) (AppRele
 		requests[i] = request
 	}
 
-	task := decodeJsonFromRequestTask[[]githubReleaseResponseJson](defaultClient)
+	task := decodeJsonFromRequestTask[githubReleaseLatestResponseJson](defaultClient)
 	job := newJob(task, requests).withWorkers(15)
 	responses, errs, err := workerPoolDo(job)
 
@@ -65,24 +63,7 @@ func FetchLatestReleasesFromGithub(repositories []string, token string) (AppRele
 			continue
 		}
 
-		releases := responses[i]
-
-		if len(releases) < 1 {
-			failed++
-			slog.Error("No releases found", "repository", repositories[i], "url", requests[i].URL)
-			continue
-		}
-
-		var liveRelease *githubReleaseResponseJson
-
-		for i := range releases {
-			release := &releases[i]
-
-			if !release.Draft && !release.PreRelease {
-				liveRelease = release
-				break
-			}
-		}
+		liveRelease := &responses[i]
 
 		if liveRelease == nil {
 			slog.Error("No live release found", "repository", repositories[i], "url", requests[i].URL)

+ 34 - 1
internal/feed/rss.go

@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/mmcdole/gofeed"
+	gofeedext "github.com/mmcdole/gofeed/extensions"
 )
 
 type RSSFeedItem struct {
@@ -97,7 +98,7 @@ func getItemsFromRSSFeedTask(request RSSFeedRequest) ([]RSSFeedItem, error) {
 			if err == nil {
 				var link string
 
-				if item.Link[0] == '/' {
+				if len(item.Link) > 0 && item.Link[0] == '/' {
 					link = item.Link
 				} else {
 					link = "/" + item.Link
@@ -145,6 +146,8 @@ func getItemsFromRSSFeedTask(request RSSFeedRequest) ([]RSSFeedItem, error) {
 
 		if item.Image != nil {
 			rssItem.ImageURL = item.Image.URL
+		} else if url := findThumbnailInItemExtensions(item); url != "" {
+			rssItem.ImageURL = url
 		} else if feed.Image != nil {
 			rssItem.ImageURL = feed.Image.URL
 		}
@@ -161,6 +164,36 @@ func getItemsFromRSSFeedTask(request RSSFeedRequest) ([]RSSFeedItem, error) {
 	return items, nil
 }
 
+func recursiveFindThumbnailInExtensions(extensions map[string][]gofeedext.Extension) string {
+	for _, exts := range extensions {
+		for _, ext := range exts {
+			if ext.Name == "thumbnail" || ext.Name == "image" {
+				if url, ok := ext.Attrs["url"]; ok {
+					return url
+				}
+			}
+
+			if ext.Children != nil {
+				if url := recursiveFindThumbnailInExtensions(ext.Children); url != "" {
+					return url
+				}
+			}
+		}
+	}
+
+	return ""
+}
+
+func findThumbnailInItemExtensions(item *gofeed.Item) string {
+	media, ok := item.Extensions["media"]
+
+	if !ok {
+		return ""
+	}
+
+	return recursiveFindThumbnailInExtensions(media)
+}
+
 func GetItemsFromRSSFeeds(requests []RSSFeedRequest) (RSSFeedItems, error) {
 	job := newJob(getItemsFromRSSFeedTask, requests).withWorkers(10)
 	feeds, errs, err := workerPoolDo(job)

+ 0 - 303
scripts/build-and-ship/main.go

@@ -1,303 +0,0 @@
-package main
-
-import (
-	"flag"
-	"fmt"
-	"os"
-	"os/exec"
-	"path"
-	"strings"
-)
-
-// bunch of spaget but it does the job for now
-// TODO: tidy up and add a proper build system with CI
-
-const buildPath = "./build"
-const archivesPath = "./build/archives"
-const executableName = "glance"
-const ownerAndRepo = "glanceapp/glance"
-const moduleName = "github.com/" + ownerAndRepo
-
-type archiveType int
-
-const (
-	archiveTypeTarGz archiveType = iota
-	archiveTypeZip
-)
-
-type buildInfo struct {
-	version string
-}
-
-type buildTarget struct {
-	os        string
-	arch      string
-	armV      int
-	extension string
-	archive   archiveType
-}
-
-var buildTargets = []buildTarget{
-	{
-		os:        "windows",
-		arch:      "amd64",
-		extension: ".exe",
-		archive:   archiveTypeZip,
-	},
-	{
-		os:        "windows",
-		arch:      "arm64",
-		extension: ".exe",
-		archive:   archiveTypeZip,
-	},
-	{
-		os:   "linux",
-		arch: "amd64",
-	},
-	{
-		os:   "linux",
-		arch: "arm64",
-	},
-	{
-		os:   "linux",
-		arch: "arm",
-		armV: 6,
-	},
-	{
-		os:   "linux",
-		arch: "arm",
-		armV: 7,
-	},
-	{
-		os:   "openbsd",
-		arch: "amd64",
-	},
-	{
-		os:   "openbsd",
-		arch: "386",
-	},
-}
-
-func hasUncommitedChanges() (bool, error) {
-	output, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
-
-	if err != nil {
-		return false, err
-	}
-
-	return len(output) > 0, nil
-}
-
-func main() {
-	flags := flag.NewFlagSet("", flag.ExitOnError)
-
-	specificTag := flags.String("tag", "", "Which tagged version to build")
-
-	err := flags.Parse(os.Args[1:])
-
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	uncommitedChanges, err := hasUncommitedChanges()
-
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	if uncommitedChanges {
-		fmt.Println("There are uncommited changes - commit, stash or discard them first")
-		os.Exit(1)
-	}
-
-	cwd, err := os.Getwd()
-
-	if err != nil {
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	_, err = os.Stat(buildPath)
-
-	if err == nil {
-		fmt.Println("Cleaning up build path")
-		os.RemoveAll(buildPath)
-	}
-
-	os.Mkdir(buildPath, 0755)
-	os.Mkdir(archivesPath, 0755)
-
-	var version string
-
-	if *specificTag == "" {
-		version, err := getVersionFromGit()
-
-		if err != nil {
-			fmt.Println(version, err)
-			os.Exit(1)
-		}
-	} else {
-		version = *specificTag
-	}
-
-	output, err := exec.Command("git", "checkout", "tags/"+version).CombinedOutput()
-
-	if err != nil {
-		fmt.Println(string(output))
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	info := buildInfo{
-		version: version,
-	}
-
-	for _, target := range buildTargets {
-		fmt.Printf("Building for %s/%s\n", target.os, target.arch)
-		if err := build(cwd, info, target); err != nil {
-			fmt.Println(err)
-			os.Exit(1)
-		}
-	}
-
-	versionTag := fmt.Sprintf("%s:%s", ownerAndRepo, version)
-	latestTag := fmt.Sprintf("%s:latest", ownerAndRepo)
-
-	fmt.Println("Building docker image")
-
-	var dockerBuildOptions = []string{
-		"docker", "build",
-		"--platform=linux/amd64,linux/arm64,linux/arm/v7",
-		"-t", versionTag,
-	}
-
-	if !strings.Contains(version, "beta") {
-		dockerBuildOptions = append(dockerBuildOptions, "-t", latestTag)
-	}
-
-	dockerBuildOptions = append(dockerBuildOptions, ".")
-
-	output, err = exec.Command("sudo", dockerBuildOptions...).CombinedOutput()
-
-	if err != nil {
-		fmt.Println(string(output))
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	var input string
-	fmt.Print("Push docker image? [y/n]: ")
-	fmt.Scanln(&input)
-
-	if input != "y" {
-		os.Exit(0)
-	}
-
-	output, err = exec.Command(
-		"sudo", "docker", "push", versionTag,
-	).CombinedOutput()
-
-	if err != nil {
-		fmt.Printf("Failed pushing %s:\n", versionTag)
-		fmt.Println(string(output))
-		fmt.Println(err)
-		os.Exit(1)
-	}
-
-	if strings.Contains(version, "beta") {
-		return
-	}
-
-	output, err = exec.Command(
-		"sudo", "docker", "push", latestTag,
-	).CombinedOutput()
-
-	if err != nil {
-		fmt.Printf("Failed pushing %s:\n", latestTag)
-		fmt.Println(string(output))
-		fmt.Println(err)
-		os.Exit(1)
-	}
-}
-
-func getVersionFromGit() (string, error) {
-	output, err := exec.Command("git", "describe", "--tags", "--abbrev=0").CombinedOutput()
-
-	if err == nil {
-		return strings.TrimSpace(string(output)), err
-	}
-
-	return string(output), err
-}
-
-func archiveFile(name string, target string, t archiveType) error {
-	var output []byte
-	var err error
-
-	if t == archiveTypeZip {
-		output, err = exec.Command("zip", "-j", path.Join(archivesPath, name+".zip"), target).CombinedOutput()
-	} else if t == archiveTypeTarGz {
-		output, err = exec.Command("tar", "-C", buildPath, "-czf", path.Join(archivesPath, name+".tar.gz"), name).CombinedOutput()
-	}
-
-	if err != nil {
-		fmt.Println(string(output))
-		return err
-	}
-
-	return nil
-}
-
-func build(workingDir string, info buildInfo, target buildTarget) error {
-	var name string
-
-	if target.arch != "arm" {
-		name = fmt.Sprintf("%s-%s-%s%s", executableName, target.os, target.arch, target.extension)
-	} else {
-		name = fmt.Sprintf("%s-%s-%sv%d", executableName, target.os, target.arch, target.armV)
-	}
-
-	binaryPath := path.Join(buildPath, name)
-
-	glancePackage := moduleName + "/internal/glance"
-
-	flags := "-s -w"
-	flags += fmt.Sprintf(" -X %s.buildVersion=%s", glancePackage, info.version)
-
-	cmd := exec.Command(
-		"go",
-		"build",
-		"--trimpath",
-		"--ldflags",
-		flags,
-		"-o",
-		binaryPath,
-	)
-
-	cmd.Dir = workingDir
-	env := append(os.Environ(), "GOOS="+target.os, "GOARCH="+target.arch, "CGO_ENABLED=0")
-
-	if target.arch == "arm" {
-		env = append(env, fmt.Sprintf("GOARM=%d", target.armV))
-	}
-
-	cmd.Env = env
-	output, err := cmd.CombinedOutput()
-
-	if err != nil {
-		fmt.Println(err)
-		fmt.Println(string(output))
-		return err
-	}
-
-	os.Chmod(binaryPath, 0755)
-
-	fmt.Println("Creating archive")
-	if err := archiveFile(name, binaryPath, target.archive); err != nil {
-		return err
-	}
-
-	return nil
-}