package main import ( crypto_tls "crypto/tls" "embed" "fmt" "io/fs" "log/slog" "os" "os/signal" "syscall" "github.com/imbytecat/voicepaste/internal/asr" "github.com/imbytecat/voicepaste/internal/config" "github.com/imbytecat/voicepaste/internal/paste" "github.com/imbytecat/voicepaste/internal/server" vpTLS "github.com/imbytecat/voicepaste/internal/tls" "github.com/imbytecat/voicepaste/internal/ws" ) //go:embed all:web/dist var webFS embed.FS var version = "dev" func main() { initLogger() slog.Info("VoicePaste", "version", version) cfg := mustLoadConfig() stopWatch := config.WatchAndReload("") defer func() { if stopWatch != nil { stopWatch() } }() initClipboard() lanIPs := mustDetectLANIPs() lanIP := lanIPs[0] tlsResult := mustSetupTLS(lanIP) printBanner(cfg, tlsResult, lanIPs) srv := createServer(cfg, lanIP, tlsResult) runWithGracefulShutdown(srv) } func initLogger() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelInfo, }))) } func mustLoadConfig() config.Config { cfg, err := config.Load("") if err != nil { slog.Error("failed to load config", "error", err) os.Exit(1) } return cfg } func initClipboard() { if err := paste.Init(); err != nil { slog.Warn("clipboard init failed, paste will be unavailable", "err", err) } } func mustDetectLANIPs() []string { lanIPs, err := server.GetLANIPs() if err != nil { slog.Error("failed to detect LAN IP", "error", err) os.Exit(1) } return lanIPs } func mustSetupTLS(lanIP string) *vpTLS.Result { tlsResult, err := vpTLS.GetTLSConfig(lanIP) if err != nil { slog.Error("TLS setup failed", "error", err) os.Exit(1) } return tlsResult } func printBanner(cfg config.Config, tlsResult *vpTLS.Result, lanIPs []string) { fmt.Println() fmt.Println("╔══════════════════════════════════════╗") fmt.Println("║ VoicePaste 就绪 ║") fmt.Println("╚══════════════════════════════════════╝") fmt.Println() printAddresses(cfg, tlsResult, lanIPs) printCertInfo(tlsResult) printAuthInfo(cfg.Server.Token) fmt.Println() fmt.Println(" 在手机浏览器中打开上方地址") fmt.Println(" 按 Ctrl+C 停止服务") fmt.Println() } func printAddresses(cfg config.Config, tlsResult *vpTLS.Result, lanIPs []string) { token := cfg.Server.Token if len(lanIPs) == 1 { host := lanIPHost(tlsResult, lanIPs[0]) fmt.Printf(" 地址: %s\n", buildURL(host, cfg.Server.Port, token)) return } fmt.Println(" 地址:") for _, ip := range lanIPs { host := lanIPHost(tlsResult, ip) fmt.Printf(" - %s\n", buildURL(host, cfg.Server.Port, token)) } } func lanIPHost(tlsResult *vpTLS.Result, ip string) string { if tlsResult != nil { return vpTLS.AnyIPHost(ip) } return ip } func printCertInfo(tlsResult *vpTLS.Result) { if tlsResult != nil { fmt.Println(" 证书: AnyIP(浏览器信任)") } else { fmt.Println(" 证书: 获取失败") } } func printAuthInfo(token string) { if token != "" { fmt.Println(" 认证: 已启用") } else { fmt.Println(" 认证: 未启用(无需 token)") } } func createServer(cfg config.Config, lanIP string, tlsResult *vpTLS.Result) *server.Server { webContent, _ := fs.Sub(webFS, "web/dist") var tlsConfig *crypto_tls.Config if tlsResult != nil { tlsConfig = tlsResult.Config } srv := server.New(cfg.Server.Token, lanIP, webContent, tlsConfig) asrFactory := buildASRFactory() wsHandler := ws.NewHandler(cfg.Server.Token, paste.Paste, asrFactory) wsHandler.Register(srv.App()) return srv } func buildASRFactory() func(chan<- ws.ServerMsg) (func([]byte), func(), error) { return func(resultCh chan<- ws.ServerMsg) (func([]byte), func(), error) { cfg := config.Get() asrCfg := asr.Config{ AppID: cfg.Doubao.AppID, AccessToken: cfg.Doubao.AccessToken, ResourceID: cfg.Doubao.ResourceID, Hotwords: cfg.ASR.Hotwords, } client, err := asr.Dial(asrCfg, resultCh) if err != nil { return nil, nil, err } sendAudio := func(pcm []byte) { if err := client.SendAudio(pcm, false); err != nil { slog.Warn("send audio to asr", "err", err) } } cleanup := func() { client.Finish() } return sendAudio, cleanup, nil } } func runWithGracefulShutdown(srv *server.Server) { go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh slog.Info("shutting down...") srv.Shutdown() }() if err := srv.Start(); err != nil { slog.Error("server error", "error", err) os.Exit(1) } } func buildURL(host string, port int, token string) string { if token != "" { return fmt.Sprintf("https://%s:%d/?token=%s", host, port, token) } return fmt.Sprintf("https://%s:%d/", host, port) }