Files
voicepaste/main.go

147 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
var webFS embed.FS
var version = "dev"
func main() {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
slog.Info("VoicePaste", "version", version)
// Load config
cfg, err := config.Load("")
if err != nil {
slog.Error("failed to load config", "error", err)
os.Exit(1)
}
// Start config hot-reload watcher
config.WatchAndReload("")
// Initialize clipboard
if err := paste.Init(); err != nil {
slog.Warn("clipboard init failed, paste will be unavailable", "err", err)
}
// Detect LAN IP
lanIP, err := server.GetLANIP()
if err != nil {
slog.Error("failed to detect LAN IP", "error", err)
os.Exit(1)
}
// Read token from config (empty = no auth required)
token := cfg.Security.Token
// TLS setup
var tlsResult *vpTLS.Result
scheme := "http"
host := lanIP
if cfg.Server.TLSAuto {
var err error
tlsResult, err = vpTLS.GetTLSConfig(lanIP)
if err != nil {
slog.Error("TLS setup failed", "error", err)
os.Exit(1)
}
scheme = "https"
host = tlsResult.Host
}
// Build URL
var url string
if token != "" {
url = fmt.Sprintf("%s://%s:%d/?token=%s", scheme, host, cfg.Server.Port, token)
} else {
url = fmt.Sprintf("%s://%s:%d/", scheme, host, cfg.Server.Port)
}
// Print connection info
fmt.Println()
fmt.Println("╔══════════════════════════════════════╗")
fmt.Println("║ VoicePaste 就绪 ║")
fmt.Println("╚══════════════════════════════════════╝")
fmt.Println()
fmt.Printf(" 地址: %s\n", url)
if tlsResult != nil && tlsResult.AnyIP {
fmt.Println(" 证书: AnyIP浏览器信任")
} else if cfg.Server.TLSAuto {
fmt.Println(" 证书: 自签名(浏览器会警告)")
}
if token != "" {
fmt.Println(" 认证: 已启用")
} else {
fmt.Println(" 认证: 未启用(无需 token")
}
fmt.Println()
printQRCode(url)
fmt.Println()
fmt.Println(" 用手机扫描二维码连接")
fmt.Println(" 按 Ctrl+C 停止服务")
fmt.Println()
// Create and start server
webContent, _ := fs.Sub(webFS, "web")
var serverTLSCfg *crypto_tls.Config
if tlsResult != nil {
serverTLSCfg = tlsResult.Config
}
srv := server.New(token, lanIP, webContent, serverTLSCfg)
// Build ASR factory from config
asrCfg := asr.Config{
AppKey: cfg.Doubao.AppKey,
AccessKey: cfg.Doubao.AccessKey,
ResourceID: cfg.Doubao.ResourceID,
}
asrFactory := func(resultCh chan<- ws.ServerMsg) (func([]byte), func(), error) {
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() {
// Send last empty frame to signal end
_ = client.SendAudio(nil, true)
client.Close()
}
return sendAudio, cleanup, nil
}
// Register WebSocket handler
wsHandler := ws.NewHandler(token, paste.Paste, asrFactory)
wsHandler.Register(srv.App())
// Graceful shutdown
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)
}
}