Files
pwa-player/grpc-backend/main.go
2025-07-27 14:28:46 +02:00

249 lines
5.7 KiB
Go

package main
import (
v1 "backend/gen"
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"path"
"regexp"
"strings"
"syscall"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/joho/godotenv"
"github.com/juli0n21/go-osu-parser/parser"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
envMap, err := godotenv.Read(".env")
if err != nil {
fmt.Println("Error reading .env file")
}
if envMap["OSU_PATH"] == "" {
fmt.Println("Osu! not found! Please paste the full path to your osu! folder.")
fmt.Println("It should start with 'C://' and can be opened from the Options menu (Open osu! folder)")
fmt.Print("Path: ")
fmt.Scanln(&osuRoot)
osuRoot = strings.TrimSpace(osuRoot)
envMap["OSU_PATH"] = osuRoot
godotenv.Write(envMap, ".env")
}
cookie := envMap["COOKIE"]
if cookie == "" {
fmt.Println("No Authentication found please follow the link to log in!\nhttps://proxy.illegalesachen.download/login")
fmt.Println("After login paste the cookie from the browser")
fmt.Print("Cookie: ")
fmt.Scanln(&cookie)
cookie = strings.TrimSpace(cookie)
envMap["COOKIE"] = cookie
godotenv.Write(envMap, ".env")
}
osuRoot := envMap["OSU_PATH"]
port := GetEnv(envMap["PORT"], ":8080")
filename := path.Join(osuRoot, "osu!.db")
osuDb, err := parser.ParseOsuDB(filename)
if err != nil {
log.Fatal(err)
}
url, err := StartCloudflared(port)
if err != nil {
log.Fatalf("Cloudflared service couldnt be started: %v", err)
}
if err = sendUrl(url, cookie); err != nil {
log.Fatalf("Couldnt Update Endpoint url with Proxy: %v", err)
}
db, sqlc, err := initDB("./data/music.db", osuDb, osuRoot)
if err != nil {
log.Fatal(err)
}
s := &Server{
Port: port,
Db: db,
OsuDir: osuRoot,
Env: envMap,
Sqlc: sqlc,
}
fmt.Println("Enjoy ur music at: https://music.illegalesachen.download (click refresh if the link hasnt updated yet)")
if err := runGrpcAndGateway(s, port); err != nil {
log.Fatalf("Failed to run servers: %v", err)
}
}
func GetEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok && value != "" {
return value
}
return fallback
}
func StartCloudflared(port string) (string, error) {
fmt.Println("Starting Cloudflared tunnel...")
cmd := exec.Command("cloudflared", "tunnel", "--url", fmt.Sprintf("http://localhost%s", port))
stderr, err := cmd.StderrPipe()
if err != nil {
return "", fmt.Errorf("Error creating StderrPipe: %v", err)
}
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("Error starting command: %v", err)
}
stderrScanner := bufio.NewScanner(stderr)
urlRegex := regexp.MustCompile(`https?://[\w.-]+\.trycloudflare\.com`)
for stderrScanner.Scan() {
line := stderrScanner.Text()
if url := urlRegex.FindString(line); url != "" {
fmt.Println("Found URL:", url)
return url, nil
}
}
if err := cmd.Wait(); err != nil {
return "", fmt.Errorf("Error waiting for command: %v", err)
}
if err := stderrScanner.Err(); err != nil {
return "", fmt.Errorf("Error reading stderr: %v", err)
}
return "", fmt.Errorf("no url found")
}
func sendUrl(endpoint, cookie string) error {
url := GetEnv("PROXY_URL", "https://proxy.illegalesachen.download/settings")
payload := struct {
Sharing *bool `json:"sharing"`
Endpoint string `json:"endpoint"`
}{
Endpoint: endpoint,
}
body, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("Error marshalling payload: %v", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("failed to create request: %v", err)
}
req.Header.Add("Content-Type", "application/json")
req.AddCookie(&http.Cookie{
Name: "session_cookie",
Value: cookie,
})
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("Error sending request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error in request: %s", resp.Status)
}
return nil
}
func runGrpcAndGateway(s *Server, port string) error {
grpcPort := ":9090"
httpPort := port
grpcLis, err := net.Listen("tcp", grpcPort)
if err != nil {
return fmt.Errorf("failed to listen on %s: %w", grpcPort, err)
}
grpcServer := grpc.NewServer()
v1.RegisterMusicBackendServer(grpcServer, s)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
gwMux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err = v1.RegisterMusicBackendHandlerFromEndpoint(ctx, gwMux, grpcPort, opts)
if err != nil {
return fmt.Errorf("failed to register grpc-gateway: %w", err)
}
mux := &http.ServeMux{}
mux.HandleFunc("/callback/", s.callback)
mux.HandleFunc("/api/v1/audio/{filepath}", s.songFile)
mux.HandleFunc("/api/v1/image/{filepath}", s.imageFile)
fileServer := http.FileServer(http.Dir("gen/swagger"))
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fileServer))
httpServer := &http.Server{
Addr: httpPort,
Handler: corsMiddleware(logRequests(mux)),
}
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//log.Printf("HTTP %s %s", r.Method, r.URL.Path)
gwMux.ServeHTTP(w, r)
}))
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
errChan := make(chan error, 2)
go func() {
log.Printf("Starting gRPC server on %s", grpcPort)
errChan <- grpcServer.Serve(grpcLis)
}()
go func() {
log.Printf("Starting HTTP gateway server on %s", httpPort)
errChan <- httpServer.ListenAndServe()
}()
select {
case <-stop:
log.Println("Shutting down servers...")
grpcServer.GracefulStop()
httpServer.Shutdown(ctx)
return nil
case err := <-errChan:
return err
}
}