Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Hua Audio 2026-03-21 09:42:09 +01:00 committed by Huaaudio
parent 49970ffbab
commit 4daaeb2a48
3 changed files with 75 additions and 10 deletions

View file

@ -70,5 +70,5 @@ type AudioChunk struct {
type VoiceControl struct { type VoiceControl struct {
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
Type string `json:"type"` // "state", "command" Type string `json:"type"` // "state", "command"
Action string `json:"action"` // "idle", "listening", "start", "stop" Action string `json:"action"` // "idle", "listening", "start", "stop", "leave"
} }

View file

@ -73,17 +73,46 @@ func streamOggOpusToDiscord(ctx context.Context, vc *discordgo.VoiceConnection,
func (c *DiscordChannel) receiveVoice(vc *discordgo.VoiceConnection, guildID string, chatID string) { func (c *DiscordChannel) receiveVoice(vc *discordgo.VoiceConnection, guildID string, chatID string) {
logger.InfoCF("discord", "Started listening for voice", map[string]any{"guild": guildID}) logger.InfoCF("discord", "Started listening for voice", map[string]any{"guild": guildID})
go func() { go func(ctx context.Context, vc *discordgo.VoiceConnection) {
time.Sleep(250 * time.Millisecond) // Wait a bit for connection to settle // Recover from potential panics if OpusSend is closed mid-send.
vc.Speaking(true) defer func() {
for i := 0; i < 5; i++ { if rec := recover(); rec != nil {
vc.OpusSend <- []byte{0xF8, 0xFF, 0xFE} logger.WarnCF("discord", "Recovered from panic while sending wake-up frames", map[string]any{
time.Sleep(20 * time.Millisecond) "error": rec,
"guild": guildID,
})
} }
vc.Speaking(false)
logger.DebugCF("discord", "Sent wake-up silence frames", nil)
}() }()
// If the voice connection or OpusSend are not available, nothing to do.
if vc == nil || vc.OpusSend == nil {
return
}
time.Sleep(250 * time.Millisecond) // Wait a bit for connection to settle
// Abort if the context has already been cancelled.
select {
case <-ctx.Done():
return
default:
}
vc.Speaking(true)
defer vc.Speaking(false)
silenceFrame := []byte{0xF8, 0xFF, 0xFE}
for i := 0; i < 5; i++ {
select {
case <-ctx.Done():
return
case vc.OpusSend <- silenceFrame:
}
time.Sleep(20 * time.Millisecond)
}
logger.DebugCF("discord", "Sent wake-up silence frames", map[string]any{"guild": guildID})
}(c.ctx, vc)
sessionID := fmt.Sprintf("discord_vc_%s", guildID) sessionID := fmt.Sprintf("discord_vc_%s", guildID)
c.bus.PublishVoiceControl(c.ctx, bus.VoiceControl{ c.bus.PublishVoiceControl(c.ctx, bus.VoiceControl{

View file

@ -29,12 +29,48 @@ type OpenAITTSProvider struct {
} }
func NewOpenAITTSProvider(apiKey string, apiBase string, proxyURL string) *OpenAITTSProvider { func NewOpenAITTSProvider(apiKey string, apiBase string, proxyURL string) *OpenAITTSProvider {
if apiBase == "" || apiBase == "https://api.openai.com/v1" { // Normalize apiBase to avoid malformed endpoints like
// "https://api.openai.com/audio/speech" when "/v1" is required.
if apiBase == "" {
apiBase = "https://api.openai.com/v1/audio/speech"
} else {
if u, err := url.Parse(apiBase); err == nil && u.Scheme != "" && u.Host != "" {
path := u.Path
if u.Host == "api.openai.com" {
// For the official OpenAI host, ensure exactly one /v1 prefix and
// that the path ends with /audio/speech.
if path == "" || path == "/" || path == "/v1" {
path = "/v1/audio/speech"
} else {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
if !strings.HasPrefix(path, "/v1/") {
path = "/v1" + strings.TrimSuffix(path, "/")
}
if !strings.HasSuffix(path, "/audio/speech") {
path = strings.TrimSuffix(path, "/") + "/audio/speech"
}
}
} else {
// For non-OpenAI hosts (e.g., proxies), preserve the existing base
// path and only ensure it ends with /audio/speech.
if !strings.HasSuffix(path, "/audio/speech") {
path = strings.TrimSuffix(path, "/") + "/audio/speech"
}
}
u.Path = path
apiBase = u.String()
} else {
// Fallback to the previous string-based behavior if parsing fails.
if apiBase == "https://api.openai.com/v1" {
apiBase = "https://api.openai.com/v1/audio/speech" apiBase = "https://api.openai.com/v1/audio/speech"
} else if !strings.HasSuffix(apiBase, "/audio/speech") { } else if !strings.HasSuffix(apiBase, "/audio/speech") {
// Just in case they provide openrouter base or standard base // Just in case they provide openrouter base or standard base
apiBase = strings.TrimSuffix(apiBase, "/") + "/audio/speech" apiBase = strings.TrimSuffix(apiBase, "/") + "/audio/speech"
} }
}
}
client := &http.Client{ client := &http.Client{
Timeout: 60 * time.Second, Timeout: 60 * time.Second,