diff options
Diffstat (limited to 'internal/processing/account/themes.go')
-rw-r--r-- | internal/processing/account/themes.go | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/internal/processing/account/themes.go b/internal/processing/account/themes.go new file mode 100644 index 000000000..4f8cc49a1 --- /dev/null +++ b/internal/processing/account/themes.go @@ -0,0 +1,151 @@ +// 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 +} |