package main import ( "crypto/rand" "embed" "encoding/base64" "flag" "fmt" "io/fs" "log" "net" "net/http" "os" "os/exec" "runtime" "time" ) //go:embed web/* var embeddedWeb embed.FS var version = "dev" func main() { noOpen := flag.Bool("no-open", false, "do not open the browser automatically") publicMode := flag.Bool("public", false, "run the public web service") listenAddress := flag.String("listen", "0.0.0.0:8080", "public web service listen address") publicOrigin := flag.String("origin", "https://lrc.flechazo.xin", "allowed public browser origin") downloadDirectory := flag.String("downloads", "", "directory containing optional local-app downloads") healthcheckURL := flag.String("healthcheck", "", "check a running service and exit") showVersion := flag.Bool("version", false, "print version and exit") flag.Parse() if *showVersion { fmt.Println(version) return } if *healthcheckURL != "" { client := &http.Client{Timeout: 3 * time.Second} response, err := client.Get(*healthcheckURL) if err != nil { log.Fatal(err) } _ = response.Body.Close() if response.StatusCode != http.StatusOK { log.Fatalf("healthcheck returned HTTP %d", response.StatusCode) } return } if *publicMode { runPublic(*listenAddress, *publicOrigin, *downloadDirectory) return } runLocal(*noOpen) } func runLocal(noOpen bool) { listener, err := net.Listen("tcp4", "127.0.0.1:0") if err != nil { log.Fatal(err) } token, err := randomToken() if err != nil { log.Fatal(err) } webFS, err := fs.Sub(embeddedWeb, "web") if err != nil { log.Fatal(err) } baseURL := "http://" + listener.Addr().String() app := newApp(baseURL, token, webFS, newQQClient()) server := &http.Server{ Handler: app.routes(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 16 * 1024, } launchURL := baseURL + "/start?t=" + token fmt.Printf("LRC Local %s\n仅监听本机:%s\n关闭此窗口即可停止。\n", version, baseURL) if !noOpen { go func() { time.Sleep(150 * time.Millisecond) if err := openBrowser(launchURL); err != nil { log.Printf("无法自动打开浏览器,请手动访问:%s", launchURL) } }() } else { fmt.Printf("请访问:%s\n", launchURL) } if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatal(err) } } func runPublic(listenAddress, origin, downloadDirectory string) { listener, err := net.Listen("tcp4", listenAddress) if err != nil { log.Fatal(err) } webFS, err := fs.Sub(embeddedWeb, "web") if err != nil { log.Fatal(err) } app, err := newPublicApp(origin, downloadDirectory, webFS, newQQClient()) if err != nil { log.Fatal(err) } server := &http.Server{ Handler: app.routes(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 5 * time.Minute, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 16 * 1024, } log.Printf("LRC Local %s public service listening on %s", version, listener.Addr()) if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatal(err) } } func randomToken() (string, error) { buf := make([]byte, 32) if _, err := rand.Read(buf); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(buf), nil } func openBrowser(url string) error { var command *exec.Cmd switch runtime.GOOS { case "windows": command = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) case "darwin": command = exec.Command("open", url) default: command = exec.Command("xdg-open", url) } command.Stdout = os.Stdout command.Stderr = os.Stderr return command.Start() }