mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 16:52:25 -06:00 
			
		
		
		
	
		
			
	
	
		
			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
							 | 
						||
| 
								 | 
							
								}
							 |