GoDoxy/internal/homepage/icon_url.go

163 lines
4.1 KiB
Go

package homepage
import (
"fmt"
"strings"
"github.com/yusing/go-proxy/internal"
E "github.com/yusing/go-proxy/internal/error"
)
type (
IconURL struct {
Value string `json:"value"`
FullValue string `json:"full_value"`
IconSource `json:"source"`
Extra *IconExtra `json:"extra"`
}
IconExtra struct {
FileType string `json:"file_type"`
Name string `json:"name"`
}
IconSource int
)
const (
IconSourceAbsolute IconSource = iota
IconSourceRelative
IconSourceWalkXCode
IconSourceSelfhSt
)
var ErrInvalidIconURL = E.New("invalid icon url")
func NewSelfhStIconURL(reference, format string) *IconURL {
return &IconURL{
Value: reference + "." + format,
FullValue: fmt.Sprintf("@selfhst/%s.%s", reference, format),
IconSource: IconSourceSelfhSt,
Extra: &IconExtra{
FileType: format,
Name: reference,
},
}
}
func NewWalkXCodeIconURL(name, format string) *IconURL {
return &IconURL{
Value: name + "." + format,
FullValue: fmt.Sprintf("@walkxcode/%s.%s", name, format),
IconSource: IconSourceWalkXCode,
Extra: &IconExtra{
FileType: format,
Name: name,
},
}
}
// HasIcon checks if the icon referenced by the IconURL exists in the cache based on its source.
// Returns false if the icon does not exist for IconSourceSelfhSt or IconSourceWalkXCode,
// otherwise returns true.
func (u *IconURL) HasIcon() bool {
if u.IconSource == IconSourceSelfhSt {
return internal.HasSelfhstIcon(u.Extra.Name, u.Extra.FileType)
}
if u.IconSource == IconSourceWalkXCode {
return internal.HasWalkxCodeIcon(u.Extra.Name, u.Extra.FileType)
}
return true
}
// Parse implements strutils.Parser.
func (u *IconURL) Parse(v string) error {
if v == "" {
return ErrInvalidIconURL
}
slashIndex := strings.Index(v, "/")
if slashIndex == -1 {
return ErrInvalidIconURL
}
u.FullValue = v
beforeSlash := v[:slashIndex]
switch beforeSlash {
case "http:", "https:":
u.Value = v
u.IconSource = IconSourceAbsolute
case "@target", "": // @target/favicon.ico, /favicon.ico
u.Value = v[slashIndex:]
u.IconSource = IconSourceRelative
if u.Value == "/" {
return ErrInvalidIconURL.Withf("%s", "empty path")
}
case "png", "svg", "webp": // walkxcode Icons
u.Value = v
u.IconSource = IconSourceWalkXCode
u.Extra = &IconExtra{
FileType: beforeSlash,
Name: strings.TrimSuffix(v[slashIndex+1:], "."+beforeSlash),
}
case "@selfhst", "@walkxcode": // selfh.st / walkxcode Icons, @selfhst/<reference>.<format>
u.Value = v[slashIndex+1:]
if beforeSlash == "@selfhst" {
u.IconSource = IconSourceSelfhSt
} else {
u.IconSource = IconSourceWalkXCode
}
parts := strings.Split(u.Value, ".")
if len(parts) != 2 {
return ErrInvalidIconURL.Withf("expect @%s/<reference>.<format>, e.g. @%s/adguard-home.webp", beforeSlash, beforeSlash)
}
reference, format := parts[0], strings.ToLower(parts[1])
if reference == "" || format == "" {
return ErrInvalidIconURL
}
switch format {
case "svg", "png", "webp":
default:
return ErrInvalidIconURL.Withf("%s", "invalid image format, expect svg/png/webp")
}
u.Extra = &IconExtra{
FileType: format,
Name: reference,
}
default:
return ErrInvalidIconURL.Withf("%s", v)
}
if u.Value == "" {
return ErrInvalidIconURL.Withf("%s", "empty")
}
if !u.HasIcon() {
return ErrInvalidIconURL.Withf("no such icon %s from %s", u.Value, beforeSlash)
}
return nil
}
func (u *IconURL) URL() string {
switch u.IconSource {
case IconSourceAbsolute:
return u.Value
case IconSourceRelative:
return "/" + u.Value
case IconSourceWalkXCode:
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/%s/%s.%s", u.Extra.FileType, u.Extra.Name, u.Extra.FileType)
case IconSourceSelfhSt:
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/selfhst/icons/%s/%s.%s", u.Extra.FileType, u.Extra.Name, u.Extra.FileType)
}
return ""
}
func (u *IconURL) String() string {
return u.FullValue
}
func (u *IconURL) MarshalText() ([]byte, error) {
return []byte(u.String()), nil
}
func (u *IconURL) UnmarshalText(data []byte) error {
return u.Parse(string(data))
}