mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 04:42:33 +02:00
298 lines
6.9 KiB
Go
298 lines
6.9 KiB
Go
package homepage
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/yusing/go-proxy/pkg/json"
|
|
|
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
|
"github.com/yusing/go-proxy/internal/common"
|
|
"github.com/yusing/go-proxy/internal/logging"
|
|
"github.com/yusing/go-proxy/internal/utils"
|
|
)
|
|
|
|
type GitHubContents struct { //! keep this, may reuse in future
|
|
Type string `json:"type"`
|
|
Path string `json:"path"`
|
|
Name string `json:"name"`
|
|
Sha string `json:"sha"`
|
|
Size int `json:"size"`
|
|
}
|
|
|
|
type (
|
|
IconsMap map[string]map[string]struct{}
|
|
IconList []string
|
|
Cache struct {
|
|
WalkxCode, Selfhst IconsMap
|
|
DisplayNames ReferenceDisplayNameMap
|
|
IconList IconList // combined into a single list
|
|
}
|
|
ReferenceDisplayNameMap map[string]string
|
|
)
|
|
|
|
func (icons *Cache) needUpdate() bool {
|
|
return len(icons.WalkxCode) == 0 || len(icons.Selfhst) == 0 || len(icons.IconList) == 0 || len(icons.DisplayNames) == 0
|
|
}
|
|
|
|
const updateInterval = 2 * time.Hour
|
|
|
|
var (
|
|
iconsCache *Cache
|
|
iconsCahceMu sync.RWMutex
|
|
lastUpdate time.Time
|
|
)
|
|
|
|
const (
|
|
walkxcodeIcons = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/tree.json"
|
|
selfhstIcons = "https://cdn.selfh.st/directory/icons.json"
|
|
)
|
|
|
|
func InitIconListCache() {
|
|
iconsCahceMu.Lock()
|
|
defer iconsCahceMu.Unlock()
|
|
|
|
iconsCache = &Cache{
|
|
WalkxCode: make(IconsMap),
|
|
Selfhst: make(IconsMap),
|
|
DisplayNames: make(ReferenceDisplayNameMap),
|
|
IconList: []string{},
|
|
}
|
|
err := utils.LoadJSONIfExist(common.IconListCachePath, iconsCache)
|
|
if err != nil {
|
|
logging.Error().Err(err).Msg("failed to load icon list cache config")
|
|
} else if len(iconsCache.IconList) > 0 {
|
|
logging.Info().
|
|
Int("icons", len(iconsCache.IconList)).
|
|
Int("display_names", len(iconsCache.DisplayNames)).
|
|
Msg("icon list cache loaded")
|
|
}
|
|
}
|
|
|
|
func ListAvailableIcons() (*Cache, error) {
|
|
iconsCahceMu.RLock()
|
|
if time.Since(lastUpdate) < updateInterval {
|
|
if !iconsCache.needUpdate() {
|
|
iconsCahceMu.RUnlock()
|
|
return iconsCache, nil
|
|
}
|
|
}
|
|
iconsCahceMu.RUnlock()
|
|
|
|
iconsCahceMu.Lock()
|
|
defer iconsCahceMu.Unlock()
|
|
|
|
logging.Info().Msg("updating icon data")
|
|
icons, err := fetchIconData()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logging.Info().
|
|
Int("icons", len(icons.IconList)).
|
|
Int("display_names", len(icons.DisplayNames)).
|
|
Msg("icons list updated")
|
|
|
|
iconsCache = icons
|
|
lastUpdate = time.Now()
|
|
|
|
err = utils.SaveJSON(common.IconListCachePath, iconsCache, 0o644)
|
|
if err != nil {
|
|
logging.Warn().Err(err).Msg("failed to save icon list cache")
|
|
}
|
|
return icons, nil
|
|
}
|
|
|
|
func SearchIcons(keyword string, limit int) ([]string, error) {
|
|
icons, err := ListAvailableIcons()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if keyword == "" {
|
|
return utils.Slice(icons.IconList, limit), nil
|
|
}
|
|
return utils.Slice(fuzzy.Find(keyword, icons.IconList), limit), nil
|
|
}
|
|
|
|
func HasWalkxCodeIcon(name string, filetype string) bool {
|
|
icons, err := ListAvailableIcons()
|
|
if err != nil {
|
|
logging.Error().Err(err).Msg("failed to list icons")
|
|
return false
|
|
}
|
|
if _, ok := icons.WalkxCode[filetype]; !ok {
|
|
return false
|
|
}
|
|
_, ok := icons.WalkxCode[filetype][name+"."+filetype]
|
|
return ok
|
|
}
|
|
|
|
func HasSelfhstIcon(name string, filetype string) bool {
|
|
icons, err := ListAvailableIcons()
|
|
if err != nil {
|
|
logging.Error().Err(err).Msg("failed to list icons")
|
|
return false
|
|
}
|
|
if _, ok := icons.Selfhst[filetype]; !ok {
|
|
return false
|
|
}
|
|
_, ok := icons.Selfhst[filetype][name+"."+filetype]
|
|
return ok
|
|
}
|
|
|
|
func GetDisplayName(reference string) (string, bool) {
|
|
icons, err := ListAvailableIcons()
|
|
if err != nil {
|
|
logging.Error().Err(err).Msg("failed to list icons")
|
|
return "", false
|
|
}
|
|
displayName, ok := icons.DisplayNames[reference]
|
|
return displayName, ok
|
|
}
|
|
|
|
func fetchIconData() (*Cache, error) {
|
|
walkxCodeIconMap, walkxCodeIconList, err := fetchWalkxCodeIcons()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
n := 0
|
|
for _, items := range walkxCodeIconMap {
|
|
n += len(items)
|
|
}
|
|
|
|
selfhstIconMap, selfhstIconList, referenceToNames, err := fetchSelfhstIcons()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Cache{
|
|
WalkxCode: walkxCodeIconMap,
|
|
Selfhst: selfhstIconMap,
|
|
DisplayNames: referenceToNames,
|
|
IconList: append(walkxCodeIconList, selfhstIconList...),
|
|
}, nil
|
|
}
|
|
|
|
/*
|
|
format:
|
|
|
|
{
|
|
"png": [
|
|
"*.png",
|
|
],
|
|
"svg": [
|
|
"*.svg",
|
|
],
|
|
"webp": [
|
|
"*.webp",
|
|
]
|
|
}
|
|
*/
|
|
func fetchWalkxCodeIcons() (IconsMap, IconList, error) {
|
|
req, err := http.NewRequest(http.MethodGet, walkxcodeIcons, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
data := make(map[string][]string)
|
|
err = json.Unmarshal(body, &data)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
icons := make(IconsMap, len(data))
|
|
iconList := make(IconList, 0, 2000)
|
|
for fileType, files := range data {
|
|
icons[fileType] = make(map[string]struct{}, len(files))
|
|
for _, icon := range files {
|
|
icons[fileType][icon] = struct{}{}
|
|
iconList = append(iconList, "@walkxcode/"+icon)
|
|
}
|
|
}
|
|
return icons, iconList, nil
|
|
}
|
|
|
|
/*
|
|
format:
|
|
|
|
{
|
|
"Name": "2FAuth",
|
|
"Reference": "2fauth",
|
|
"SVG": "Yes",
|
|
"PNG": "Yes",
|
|
"WebP": "Yes",
|
|
"Light": "Yes",
|
|
"Category": "Self-Hosted",
|
|
"CreatedAt": "2024-08-16 00:27:23+00:00"
|
|
}
|
|
*/
|
|
func fetchSelfhstIcons() (IconsMap, IconList, ReferenceDisplayNameMap, error) {
|
|
type SelfhStIcon struct {
|
|
Name string `json:"Name"`
|
|
Reference string `json:"Reference"`
|
|
SVG string `json:"SVG"`
|
|
PNG string `json:"PNG"`
|
|
WebP string `json:"WebP"`
|
|
// Light string
|
|
// Category string
|
|
// CreatedAt string
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, selfhstIcons, nil)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
data := make([]SelfhStIcon, 0, 2000)
|
|
err = json.Unmarshal(body, &data)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
iconList := make(IconList, 0, len(data)*3)
|
|
icons := make(IconsMap)
|
|
icons["svg"] = make(map[string]struct{}, len(data))
|
|
icons["png"] = make(map[string]struct{}, len(data))
|
|
icons["webp"] = make(map[string]struct{}, len(data))
|
|
|
|
referenceToNames := make(ReferenceDisplayNameMap, len(data))
|
|
|
|
for _, item := range data {
|
|
if item.SVG == "Yes" {
|
|
icons["svg"][item.Reference+".svg"] = struct{}{}
|
|
iconList = append(iconList, "@selfhst/"+item.Reference+".svg")
|
|
}
|
|
if item.PNG == "Yes" {
|
|
icons["png"][item.Reference+".png"] = struct{}{}
|
|
iconList = append(iconList, "@selfhst/"+item.Reference+".png")
|
|
}
|
|
if item.WebP == "Yes" {
|
|
icons["webp"][item.Reference+".webp"] = struct{}{}
|
|
iconList = append(iconList, "@selfhst/"+item.Reference+".webp")
|
|
}
|
|
referenceToNames[item.Reference] = item.Name
|
|
}
|
|
|
|
return icons, iconList, referenceToNames, nil
|
|
}
|