152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
|
// GoToSocial
|
||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU Affero General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU Affero General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Affero General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
package account
|
||
|
|
||
|
import (
|
||
|
"cmp"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"regexp"
|
||
|
"slices"
|
||
|
"strings"
|
||
|
|
||
|
"codeberg.org/gruf/go-bytesize"
|
||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||
|
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
themeTitleRegex = regexp.MustCompile(`(?m)^\ *theme-title:(.*)$`)
|
||
|
themeDescriptionRegex = regexp.MustCompile(`(?m)^\ *theme-description:(.*)$`)
|
||
|
)
|
||
|
|
||
|
// GetThemes returns available account css themes.
|
||
|
func (p *Processor) ThemesGet() []apimodel.Theme {
|
||
|
return p.converter.ThemesToAPIThemes(p.themes.SortedByTitle)
|
||
|
}
|
||
|
|
||
|
// Themes represents an in-memory
|
||
|
// storage structure for themes.
|
||
|
type Themes struct {
|
||
|
// Themes sorted alphabetically
|
||
|
// by title (case insensitive).
|
||
|
SortedByTitle []*gtsmodel.Theme
|
||
|
|
||
|
// ByFileName contains themes retrievable
|
||
|
// by their filename eg., `light-blurple.css`.
|
||
|
ByFileName map[string]*gtsmodel.Theme
|
||
|
}
|
||
|
|
||
|
// PopulateThemes parses available account CSS
|
||
|
// themes from the web assets themes directory.
|
||
|
func PopulateThemes() *Themes {
|
||
|
webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
|
||
|
if err != nil {
|
||
|
log.Panicf(nil, "error getting abs path for web assets: %v", err)
|
||
|
}
|
||
|
|
||
|
themesAbsFilePath := filepath.Join(webAssetsAbsFilePath, "themes")
|
||
|
themesFiles, err := os.ReadDir(themesAbsFilePath)
|
||
|
if err != nil {
|
||
|
log.Warnf(nil, "error reading themes at %s: %v", themesAbsFilePath, err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
themes := &Themes{
|
||
|
ByFileName: make(map[string]*gtsmodel.Theme),
|
||
|
}
|
||
|
|
||
|
for _, f := range themesFiles {
|
||
|
// Ignore nested directories.
|
||
|
if f.IsDir() {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Ignore weird files.
|
||
|
info, err := f.Info()
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Ignore really big files.
|
||
|
if info.Size() > int64(bytesize.MiB) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Get just the name of the
|
||
|
// file, eg `blurple-light.css`.
|
||
|
fileName := f.Name()
|
||
|
|
||
|
// Get just the `.css` part.
|
||
|
extensionWithDot := filepath.Ext(fileName)
|
||
|
|
||
|
// Remove any leading `.`
|
||
|
extension := strings.TrimPrefix(extensionWithDot, ".")
|
||
|
|
||
|
// Ignore non-css files.
|
||
|
if extension != "css" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Load the file contents.
|
||
|
path := filepath.Join(themesAbsFilePath, fileName)
|
||
|
contents, err := os.ReadFile(path)
|
||
|
if err != nil {
|
||
|
log.Warnf(nil, "error reading css theme at %s: %v", path, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Try to parse a title and description
|
||
|
// for this theme from the file itself.
|
||
|
var themeTitle string
|
||
|
titleMatches := themeTitleRegex.FindSubmatch(contents)
|
||
|
if len(titleMatches) == 2 {
|
||
|
themeTitle = strings.TrimSpace(string(titleMatches[1]))
|
||
|
} else {
|
||
|
// Fall back to file name
|
||
|
// without `.css` suffix.
|
||
|
themeTitle = strings.TrimSuffix(fileName, ".css")
|
||
|
}
|
||
|
|
||
|
var themeDescription string
|
||
|
descMatches := themeDescriptionRegex.FindSubmatch(contents)
|
||
|
if len(descMatches) == 2 {
|
||
|
themeDescription = strings.TrimSpace(string(descMatches[1]))
|
||
|
}
|
||
|
|
||
|
theme := >smodel.Theme{
|
||
|
Title: themeTitle,
|
||
|
Description: themeDescription,
|
||
|
FileName: fileName,
|
||
|
}
|
||
|
|
||
|
themes.SortedByTitle = append(themes.SortedByTitle, theme)
|
||
|
themes.ByFileName[fileName] = theme
|
||
|
}
|
||
|
|
||
|
// Sort themes alphabetically
|
||
|
// by title (case insensitive).
|
||
|
slices.SortFunc(themes.SortedByTitle, func(a, b *gtsmodel.Theme) int {
|
||
|
return cmp.Compare(strings.ToLower(a.Title), strings.ToLower(b.Title))
|
||
|
})
|
||
|
|
||
|
return themes
|
||
|
}
|