Files
relibre/main.go
2025-10-18 02:20:30 -04:00

158 lines
3.4 KiB
Go

// main.go
package main
import (
"bufio"
"embed"
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
webview "github.com/webview/webview_go"
)
var content embed.FS
type saveReq struct {
Filename string `json:"filename"`
Content string `json:"content"`
}
type saveResp struct {
Path string `json:"path"`
}
func main() {
subFS, err := fs.Sub(content, ".")
if err != nil {
log.Fatalf("fs.Sub error: %v", err)
}
mux := http.NewServeMux()
mux.Handle("/", http.FileServer(http.FS(subFS)))
mux.HandleFunc("/save", func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() { log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start)) }()
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var req saveReq
if err := json.NewDecoder(io.LimitReader(r.Body, 10<<20)).Decode(&req); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if req.Filename == "" {
req.Filename = fmt.Sprintf("relibre-%d.html", time.Now().Unix())
}
req.Filename = sanitizeFilename(req.Filename)
dir := downloadsDir()
if err := os.MkdirAll(dir, 0o755); err != nil {
http.Error(w, "mkdir failed", http.StatusInternalServerError)
return
}
path := filepath.Join(dir, req.Filename)
if err := os.WriteFile(path, []byte(req.Content), 0o644); err != nil {
http.Error(w, "write failed", http.StatusInternalServerError)
return
}
log.Printf("Saved HTML -> %s", path)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(saveResp{Path: path})
})
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
mux.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
log.Fatalf("listen: %v", err)
}
addr := ln.Addr().String()
srv := &http.Server{Handler: handler, ReadHeaderTimeout: 5 * time.Second}
go func() {
log.Printf("HTTP listening on http://%s", addr)
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
log.Fatalf("http serve: %v", err)
}
}()
debug := true
w := webview.New(debug)
defer w.Destroy()
w.SetTitle("Relibre")
w.SetSize(1100, 750, webview.HintNone)
w.Navigate(fmt.Sprintf("http://%s", addr))
w.Run()
_ = srv.Close()
if runtime.GOOS == "linux" {
time.Sleep(100 * time.Millisecond)
}
}
func sanitizeFilename(name string) string {
name = strings.TrimSpace(name)
if name == "" {
return "relibre.html"
}
if !strings.HasSuffix(strings.ToLower(name), ".html") {
name += ".html"
}
re := regexp.MustCompile(`[<>:"/\\|?*\x00-\x1F]`)
return re.ReplaceAllString(name, "_")
}
func downloadsDir() string {
if p := readXDGDownloads(); p != "" {
return p
}
home, _ := os.UserHomeDir()
if home == "" {
return "./"
}
return filepath.Join(home, "Downloads")
}
func readXDGDownloads() string {
home, _ := os.UserHomeDir()
if home == "" {
return ""
}
path := filepath.Join(home, ".config", "user-dirs.dirs")
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, "XDG_DOWNLOAD_DIR=") {
raw := strings.TrimPrefix(line, "XDG_DOWNLOAD_DIR=")
raw = strings.Trim(raw, `"`)
raw = strings.ReplaceAll(raw, "$HOME", home)
return raw
}
}
return ""
}