Files
voicepaste/main.go

156 lines
4.1 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 IPs
lanIPs, err := server.GetLANIPs()
if err != nil {
slog.Error("failed to detect LAN IP", "error", err)
os.Exit(1)
}
lanIP := lanIPs[0] // Use first IP for TLS and server binding
// 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
}
// Print connection info
fmt.Println()
fmt.Println("╔══════════════════════════════════════╗")
fmt.Println("║ VoicePaste 就绪 ║")
fmt.Println("╚══════════════════════════════════════╝")
fmt.Println()
// Print all accessible addresses
if len(lanIPs) == 1 {
fmt.Printf(" 地址: %s\n", buildURL(scheme, host, cfg.Server.Port, token))
} else {
fmt.Println(" 地址:")
for _, ip := range lanIPs {
h := ip
if tlsResult != nil {
h = tlsResult.Host
}
fmt.Printf(" - %s\n", buildURL(scheme, h, cfg.Server.Port, token))
}
}
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()
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() {
client.Finish()
}
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)
}
}
func buildURL(scheme, host string, port int, token string) string {
if token != "" {
return fmt.Sprintf("%s://%s:%d/?token=%s", scheme, host, port, token)
}
return fmt.Sprintf("%s://%s:%d/", scheme, host, port)
}