backend working snap just before avatar toggle

main
czoczo 7 months ago
parent 3eaa035146
commit 91f717008f
  1. 1
      webpage/backend/.gitignore
  2. 142
      webpage/backend/main.go

@ -0,0 +1 @@
BetterBashRepo

@ -1,6 +1,7 @@
package main package main
import ( import (
// "bufio" // Keep for other potential uses, though not strictly needed for the new bb.sh logic if using SplitN
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
@ -9,7 +10,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" // "regexp" // Keep for other potential uses, though not for bb.sh color insertion
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -22,27 +23,15 @@ const (
) )
var ( var (
// requestCounter stores the number of HTTP requests (excluding /stats and /reload).
requestCounter uint64 requestCounter uint64
// repoMutex protects git operations like clone/pull repoMutex sync.Mutex
repoMutex sync.Mutex
) )
// colorComponentKeys defines the names for the color components in their encoding order.
var colorComponentKeys = []string{ var colorComponentKeys = []string{
"PRIMARY_COLOR", "PRIMARY_COLOR", "SECONDARY_COLOR", "ROOT_COLOR", "TIME_COLOR",
"SECONDARY_COLOR", "ERR_COLOR", "SEPARATOR_COLOR", "BORDCOL", "PATH_COLOR",
"ROOT_COLOR",
"TIME_COLOR",
"ERR_COLOR",
"SEPARATOR_COLOR",
"BORDCOL",
"PATH_COLOR",
// The 9th component (C9, potentially "Reset") is decoded but not listed for output.
} }
// decodeColorLogic processes the encoded data string and returns color information.
// It returns a map of color keys to bash color values, a formatted string list of these, and an error.
func decodeColorLogic(encodedData string) (map[string]string, string, error) { func decodeColorLogic(encodedData string) (map[string]string, string, error) {
standardBase64 := strings.ReplaceAll(encodedData, "-", "+") standardBase64 := strings.ReplaceAll(encodedData, "-", "+")
standardBase64 = strings.ReplaceAll(standardBase64, "_", "/") standardBase64 = strings.ReplaceAll(standardBase64, "_", "/")
@ -66,12 +55,12 @@ func decodeColorLogic(encodedData string) (map[string]string, string, error) {
fiveBitValues[5] = (b[3] & 0x7C) >> 2 fiveBitValues[5] = (b[3] & 0x7C) >> 2
fiveBitValues[6] = ((b[3] & 0x03) << 3) | (b[4] >> 5) fiveBitValues[6] = ((b[3] & 0x03) << 3) | (b[4] >> 5)
fiveBitValues[7] = b[4] & 0x1F fiveBitValues[7] = b[4] & 0x1F
fiveBitValues[8] = b[5] >> 3 // C9, not used in direct output string but decoded fiveBitValues[8] = b[5] >> 3
colorsMap := make(map[string]string) colorsMap := make(map[string]string)
var resultList strings.Builder var resultList strings.Builder
for i := 0; i < 8; i++ { // Only first 8 components for output for i := 0; i < 8; i++ {
val5bit := fiveBitValues[i] val5bit := fiveBitValues[i]
baseColor07 := (val5bit >> 2) & 0x07 baseColor07 := (val5bit >> 2) & 0x07
lightBit := (val5bit >> 1) & 0x01 lightBit := (val5bit >> 1) & 0x01
@ -89,13 +78,13 @@ func decodeColorLogic(encodedData string) (map[string]string, string, error) {
bashColor := fmt.Sprintf(`\[\033[%d;%dm\]`, styleAttr, actualAnsiCode) bashColor := fmt.Sprintf(`\[\033[%d;%dm\]`, styleAttr, actualAnsiCode)
colorsMap[colorComponentKeys[i]] = bashColor colorsMap[colorComponentKeys[i]] = bashColor
// Ensure each definition is on a new line, directly usable in shell script
resultList.WriteString(fmt.Sprintf("%s='%s'\n", colorComponentKeys[i], bashColor)) resultList.WriteString(fmt.Sprintf("%s='%s'\n", colorComponentKeys[i], bashColor))
} }
// Remove the last newline character from the block of definitions if present for cleaner insertion
return colorsMap, strings.TrimSpace(resultList.String()), nil return colorsMap, strings.TrimSuffix(resultList.String(), "\n"), nil
} }
// serveDecodedColorsOnlyHandler serves only the decoded color definitions.
func serveDecodedColorsOnlyHandler(w http.ResponseWriter, r *http.Request, encodedData string) { func serveDecodedColorsOnlyHandler(w http.ResponseWriter, r *http.Request, encodedData string) {
_, formattedOutput, err := decodeColorLogic(encodedData) _, formattedOutput, err := decodeColorLogic(encodedData)
if err != nil { if err != nil {
@ -103,18 +92,21 @@ func serveDecodedColorsOnlyHandler(w http.ResponseWriter, r *http.Request, encod
return return
} }
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, formattedOutput) // The formattedOutput from decodeColorLogic is already KEY='VAL'\nKEY2='VAL2'
// So it prints multiple lines as intended.
fmt.Fprintln(w, formattedOutput)
} }
// serveFileWithColorsHandler serves files from the repo, potentially modifying bb.sh. // serveFileWithColorsHandler serves files from the repo, potentially modifying bb.sh.
func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedData string, requestedFilePath string) { func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedData string, requestedFilePath string) {
colorsMap, _, err := decodeColorLogic(encodedData) // Note: The 'colorsMap' is not strictly needed for the new bb.sh logic,
// as 'formattedColorDefinitions' is used directly. But decodeColorLogic provides it.
_, formattedColorDefinitions, err := decodeColorLogic(encodedData)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Failed to decode colors: %v", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Failed to decode colors: %v", err), http.StatusBadRequest)
return return
} }
// Sanitize file path
cleanFilePath := filepath.Clean(requestedFilePath) cleanFilePath := filepath.Clean(requestedFilePath)
if strings.HasPrefix(cleanFilePath, "..") || strings.HasPrefix(cleanFilePath, "/") { if strings.HasPrefix(cleanFilePath, "..") || strings.HasPrefix(cleanFilePath, "/") {
http.Error(w, "Invalid file path.", http.StatusBadRequest) http.Error(w, "Invalid file path.", http.StatusBadRequest)
@ -122,8 +114,6 @@ func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedD
} }
fullPath := filepath.Join(localRepoPath, cleanFilePath) fullPath := filepath.Join(localRepoPath, cleanFilePath)
// Security check: ensure the path is still within the localRepoPath
absRepoPath, _ := filepath.Abs(localRepoPath) absRepoPath, _ := filepath.Abs(localRepoPath)
absFilePath, _ := filepath.Abs(fullPath) absFilePath, _ := filepath.Abs(fullPath)
if !strings.HasPrefix(absFilePath, absRepoPath) { if !strings.HasPrefix(absFilePath, absRepoPath) {
@ -145,51 +135,71 @@ func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedD
return return
} }
if cleanFilePath == bbShellPath { if cleanFilePath == bbShellPath {
// Special handling for prompt/bb.sh originalContentBytes, err := os.ReadFile(fullPath)
content, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Error reading %s: %v", bbShellPath, err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Error reading %s: %v", bbShellPath, err), http.StatusInternalServerError)
return return
} }
originalContent := string(originalContentBytes)
var finalScript strings.Builder
// Split the original content to find the shebang and the rest of the script
lines := strings.SplitN(originalContent, "\n", 2)
firstLineOriginal := ""
restOfScript := ""
modifiedScript := string(content) if len(lines) > 0 {
for key, colorValue := range colorsMap { firstLineOriginal = lines[0]
// Regex to find lines like `KEY='...'`, `export KEY="..."`, or `KEY=value` }
// It captures `export ` (if present) and the key name.
// It replaces the whole line. // Correctly assign restOfScript
// We need to be careful with shell syntax. For simplicity, we assume KEY='...' if len(lines) > 1 {
// For robust parsing, a proper shell parser would be needed. restOfScript = lines[1]
// This regex looks for `KEY=`, potentially with `export` prefix, and replaces value. } else if len(lines) == 1 { // Script might be a single line or empty
// It assumes the value is single-quoted or simply the rest of the line. if originalContent == firstLineOriginal { // single line script
// Example: PRIMARY_COLOR='...' or export PRIMARY_COLOR='...' restOfScript = ""
regex := regexp.MustCompile(fmt.Sprintf(`^(export\s+)?%s\s*=\s*('.*?'|".*?"|[^#\s]*)`, regexp.QuoteMeta(key))) } else { // empty script, lines[0] would be ""
replacement := fmt.Sprintf("${1}%s='%s'", key, colorValue) restOfScript = ""
modifiedScript = regex.ReplaceAllString(modifiedScript, replacement) }
}
if strings.HasPrefix(firstLineOriginal, "#!") {
finalScript.WriteString(firstLineOriginal) // Write shebang
finalScript.WriteString("\n") // Newline after shebang
finalScript.WriteString(formattedColorDefinitions) // This is KEY='VAL'\nKEY2='VAL2'
finalScript.WriteString("\n") // Ensure a newline after the injected block
if restOfScript != "" {
finalScript.WriteString(restOfScript)
}
} else {
// No shebang, or script didn't start with it.
// Per revised requirement, we should still try to inject.
// Prepend colors to the original content.
finalScript.WriteString(formattedColorDefinitions)
finalScript.WriteString("\n")
finalScript.WriteString(originalContent)
} }
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, modifiedScript) fmt.Fprint(w, finalScript.String())
} else { } else {
// Serve other files as is
http.ServeFile(w, r, fullPath) http.ServeFile(w, r, fullPath)
} }
} }
// statsReportHandler serves the HTTP request counter.
func statsReportHandler(w http.ResponseWriter, r *http.Request) { func statsReportHandler(w http.ResponseWriter, r *http.Request) {
count := atomic.LoadUint64(&requestCounter) count := atomic.LoadUint64(&requestCounter)
w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "%d", count) fmt.Fprintf(w, "%d", count)
} }
// rootPathHandler handles requests to the bare "/" path.
func rootPathHandler(w http.ResponseWriter, r *http.Request) { func rootPathHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Please provide an encoded string in the URL path (e.g., /YourEncodedString) or a file path (e.g. /YourEncodedString/path/to/file.sh)", http.StatusBadRequest) http.Error(w, "Please provide an encoded string in the URL path (e.g., /YourEncodedString) or a file path (e.g. /YourEncodedString/path/to/file.sh)", http.StatusBadRequest)
} }
// runGitCommand executes a git command and logs its output.
func runGitCommand(dir string, args ...string) error { func runGitCommand(dir string, args ...string) error {
cmd := exec.Command("git", args...) cmd := exec.Command("git", args...)
if dir != "" { if dir != "" {
@ -214,7 +224,6 @@ func runGitCommand(dir string, args ...string) error {
return nil return nil
} }
// setupRepo clones the repository if it doesn't exist locally.
func setupRepo() error { func setupRepo() error {
repoMutex.Lock() repoMutex.Lock()
defer repoMutex.Unlock() defer repoMutex.Unlock()
@ -227,23 +236,15 @@ func setupRepo() error {
log.Printf("Repository cloned successfully into %s.", localRepoPath) log.Printf("Repository cloned successfully into %s.", localRepoPath)
} else { } else {
log.Printf("Local repository found at %s. Skipping clone.", localRepoPath) log.Printf("Local repository found at %s. Skipping clone.", localRepoPath)
// Optionally, pull latest on startup too
// log.Printf("Pulling latest changes for %s...", localRepoPath)
// if err := runGitCommand(localRepoPath, "pull", "origin", "master"); err != nil {
// return fmt.Errorf("failed to pull repository on startup: %w", err)
// }
// log.Printf("Repository updated successfully.")
} }
return nil return nil
} }
// reloadRepoHandler handles the /reload endpoint to pull the latest from git.
func reloadRepoHandler(w http.ResponseWriter, r *http.Request) { func reloadRepoHandler(w http.ResponseWriter, r *http.Request) {
repoMutex.Lock() repoMutex.Lock()
defer repoMutex.Unlock() defer repoMutex.Unlock()
log.Printf("Attempting to pull latest changes for repository at %s", localRepoPath) log.Printf("Attempting to pull latest changes for repository at %s", localRepoPath)
// Ensure the repo exists before trying to pull
if _, err := os.Stat(localRepoPath); os.IsNotExist(err) { if _, err := os.Stat(localRepoPath); os.IsNotExist(err) {
log.Printf("Local repository at %s does not exist. Cloning first.", localRepoPath) log.Printf("Local repository at %s does not exist. Cloning first.", localRepoPath)
if err := runGitCommand("", "clone", gitRepoURL, localRepoPath); err != nil { if err := runGitCommand("", "clone", gitRepoURL, localRepoPath); err != nil {
@ -256,15 +257,10 @@ func reloadRepoHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Attempt to reset any local changes and then pull
// This is a forceful way to ensure it matches origin/master
// Be cautious with `git reset --hard` if local changes could be important,
// but for a cache/mirror this is often desired.
err := runGitCommand(localRepoPath, "fetch", "origin") err := runGitCommand(localRepoPath, "fetch", "origin")
if err == nil { if err == nil {
err = runGitCommand(localRepoPath, "reset", "--hard", "origin/master") err = runGitCommand(localRepoPath, "reset", "--hard", "origin/master")
} }
// Fallback to simple pull if reset fails or as primary strategy
if err != nil { if err != nil {
log.Printf("Hard reset failed (or fetch failed): %v. Attempting simple pull.", err) log.Printf("Hard reset failed (or fetch failed): %v. Attempting simple pull.", err)
err = runGitCommand(localRepoPath, "pull", "origin", "master") err = runGitCommand(localRepoPath, "pull", "origin", "master")
@ -282,62 +278,49 @@ func reloadRepoHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Repository reloaded successfully.") fmt.Fprintln(w, "Repository reloaded successfully.")
} }
// mainRouter is the primary HTTP handler.
func mainRouter(w http.ResponseWriter, r *http.Request) { func mainRouter(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/stats" { if r.URL.Path == "/stats" {
statsReportHandler(w, r) statsReportHandler(w, r)
return return
} }
// Increment request counter for non-stats paths.
// Reload is also a functional path so we count it.
atomic.AddUint64(&requestCounter, 1) atomic.AddUint64(&requestCounter, 1)
if r.URL.Path == "/reload" { if r.URL.Path == "/reload" {
reloadRepoHandler(w, r) reloadRepoHandler(w, r)
return return
} }
if r.URL.Path == "/" { if r.URL.Path == "/" {
rootPathHandler(w, r) rootPathHandler(w, r)
return return
} }
// Path structure: /<encoded_data>[/<file_path>]
trimmedPath := strings.TrimPrefix(r.URL.Path, "/") trimmedPath := strings.TrimPrefix(r.URL.Path, "/")
parts := strings.SplitN(trimmedPath, "/", 2) parts := strings.SplitN(trimmedPath, "/", 2)
encodedData := parts[0] encodedData := parts[0]
if encodedData == "" { // Path was just "/" or "//..."
rootPathHandler(w, r) // Or a more specific error if encodedData == "" {
rootPathHandler(w, r)
return return
} }
if len(parts) == 1 { if len(parts) == 1 {
// Only /<encoded_data>
serveDecodedColorsOnlyHandler(w, r, encodedData) serveDecodedColorsOnlyHandler(w, r, encodedData)
} else if len(parts) == 2 { } else if len(parts) == 2 {
// /<encoded_data>/<file_path>
filePath := parts[1] filePath := parts[1]
if filePath == "" { // e.g. /encodedData/ if filePath == "" {
http.Error(w, "File path cannot be empty if a second slash is provided.", http.StatusBadRequest) http.Error(w, "File path cannot be empty if a second slash is provided.", http.StatusBadRequest)
return return
} }
serveFileWithColorsHandler(w, r, encodedData, filePath) serveFileWithColorsHandler(w, r, encodedData, filePath)
} }
// SplitN with 2 parts will always result in len 1 or 2 if input is not empty.
// If trimmedPath was empty, parts would be {""}, len 1. This is caught by encodedData==""
} }
func main() { func main() {
// Clone the repository at startup if it doesn't exist
if err := setupRepo(); err != nil { if err := setupRepo(); err != nil {
log.Fatalf("❌ Failed to setup repository: %s\n", err) log.Fatalf("❌ Failed to setup repository: %s\n", err)
// Depending on strictness, you might want to os.Exit(1) or try to run without the repo functionality
} }
http.HandleFunc("/", mainRouter) http.HandleFunc("/", mainRouter)
port := "8080" port := "8080"
if p := os.Getenv("PORT"); p != "" { if p := os.Getenv("PORT"); p != "" {
port = p port = p
@ -346,7 +329,7 @@ func main() {
log.Printf("🎨 Color Theme Backend & File Server starting on port %s (e.g., http://localhost:%s)\n", port, port) log.Printf("🎨 Color Theme Backend & File Server starting on port %s (e.g., http://localhost:%s)\n", port, port)
log.Printf("Git Repo URL: %s", gitRepoURL) log.Printf("Git Repo URL: %s", gitRepoURL)
log.Printf("Local Repo Path: ./%s", localRepoPath) log.Printf("Local Repo Path: ./%s", localRepoPath)
log.Printf("Special file for color replacement: %s", bbShellPath) log.Printf("Special file for color injection: %s", bbShellPath)
log.Printf("Endpoints:") log.Printf("Endpoints:")
log.Printf(" GET /<encoded_color_data> - Show color definitions") log.Printf(" GET /<encoded_color_data> - Show color definitions")
log.Printf(" GET /<encoded_color_data>/<path> - Serve file from repo (e.g., /VcrS_H8A/removebb.sh)") log.Printf(" GET /<encoded_color_data>/<path> - Serve file from repo (e.g., /VcrS_H8A/removebb.sh)")
@ -355,7 +338,6 @@ func main() {
log.Printf(" GET /stats - Show request count") log.Printf(" GET /stats - Show request count")
log.Printf(" GET / - Show usage instructions") log.Printf(" GET / - Show usage instructions")
if err := http.ListenAndServe(":"+port, nil); err != nil { if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("❌ Failed to start server: %s\n", err) log.Fatalf("❌ Failed to start server: %s\n", err)
} }

Loading…
Cancel
Save