@@ -165,8 +165,9 @@ func run(config Config) error {
165165}
166166
167167// fetchArticle downloads and extracts text from the given URL
168- func fetchArticle (url string ) (string , string , error ) {
168+ func fetchArticle (url string ) (content , title string , err error ) {
169169 // Fetch the page
170+ // #nosec G107 -- URL is provided by command-line flag
170171 resp , err := http .Get (url )
171172 if err != nil {
172173 return "" , "" , err
@@ -184,21 +185,21 @@ func fetchArticle(url string) (string, string, error) {
184185 }
185186
186187 // Extract title
187- title : = doc .Find ("title" ).Text ()
188+ title = doc .Find ("title" ).Text ()
188189
189190 // Extract article content - this is a simplified approach
190191 var articleText strings.Builder
191192
192193 // First try to find article content in common containers
193194 article := doc .Find ("article, .article, .post, .content, main" )
194195 if article .Length () > 0 {
195- article .Find ("p" ).Each (func (i int , s * goquery.Selection ) {
196+ article .Find ("p" ).Each (func (_ int , s * goquery.Selection ) {
196197 articleText .WriteString (s .Text ())
197198 articleText .WriteString ("\n \n " )
198199 })
199200 } else {
200201 // Fallback to all paragraphs
201- doc .Find ("p" ).Each (func (i int , s * goquery.Selection ) {
202+ doc .Find ("p" ).Each (func (_ int , s * goquery.Selection ) {
202203 // Skip very short paragraphs which are likely not article content
203204 if len (s .Text ()) > 50 {
204205 articleText .WriteString (s .Text ())
@@ -208,7 +209,7 @@ func fetchArticle(url string) (string, string, error) {
208209 }
209210
210211 // Limit article length for API calls
211- content : = articleText .String ()
212+ content = articleText .String ()
212213 if len (content ) > 8000 {
213214 content = content [:8000 ] + "..."
214215 }
@@ -232,7 +233,7 @@ type OpenAIRequest struct {
232233
233234// prepareHostDescriptions formats host information for the prompt
234235func prepareHostDescriptions (hosts []Host ) string {
235- var hostDescriptions []string
236+ hostDescriptions := make ( []string , 0 , len ( hosts ))
236237 for _ , host := range hosts {
237238 hostDescriptions = append (hostDescriptions ,
238239 fmt .Sprintf ("%s (%s): %s" , host .Name , host .Gender , host .Character ))
@@ -241,7 +242,7 @@ func prepareHostDescriptions(hosts []Host) string {
241242}
242243
243244// createDiscussionPrompt creates the system prompt for the discussion
244- func createDiscussionPrompt (hostDescriptions string , targetMessages int , targetDuration int ) string {
245+ func createDiscussionPrompt (hostDescriptions string , targetMessages , targetDuration int ) string {
245246 return fmt .Sprintf (`Generate a heated and passionate tech podcast discussion in Russian language between these hosts about the following article:
246247
247248%s
@@ -477,14 +478,14 @@ func generateSpeechSegments(messages []Message, hostMap map[string]struct {
477478 msg .Host , i + 1 , len (messages ))
478479
479480 // Generate speech with OpenAI TTS
480- audioData , err := generateOpenAITTS (msg .Content , "ru" , gender , voice , speechSpeed , apiKey )
481+ audioData , err := generateOpenAITTS (msg .Content , gender , voice , speechSpeed , apiKey )
481482 if err != nil {
482483 return nil , fmt .Errorf ("failed to generate speech for message %d: %w" , i , err )
483484 }
484485
485486 // Create a file for the audio
486487 filename := fmt .Sprintf ("%s/segment_%03d.mp3" , tempDir , i )
487- err = os .WriteFile (filename , audioData , 0644 )
488+ err = os .WriteFile (filename , audioData , 0o600 )
488489 if err != nil {
489490 return nil , fmt .Errorf ("failed to write audio data: %w" , err )
490491 }
@@ -514,6 +515,7 @@ func streamToIcecast(concatFile string, config Config) error {
514515 icecastURL ,
515516 }
516517
518+ // #nosec G204 -- Arguments are constructed internally, not from external input
517519 cmd := exec .Command ("ffmpeg" , args ... )
518520 cmd .Stdout = os .Stdout
519521 cmd .Stderr = os .Stderr
@@ -611,7 +613,7 @@ func speechGenerationWorker(requestChan <-chan SpeechGenerationRequest, resultCh
611613 case req := <- requestChan :
612614 segmentStartTime := time .Now ()
613615 fmt .Printf ("Generating speech for message %d from %s...\n " , req .index , req .msg .Host )
614- audioData , err := generateOpenAITTS (req .msg .Content , "ru" , req .gender , req .voice , req .speed , req .apiKey )
616+ audioData , err := generateOpenAITTS (req .msg .Content , req .gender , req .voice , req .speed , req .apiKey )
615617 if err != nil {
616618 fmt .Printf ("Error generating speech for message %d: %v\n " , req .index , err )
617619 } else {
@@ -658,15 +660,15 @@ func saveToConcatFile(tempDir string, audioFiles []string) (string, error) {
658660 for _ , file := range audioFiles {
659661 concatContent .WriteString (fmt .Sprintf ("file '%s'\n " , file ))
660662 }
661- err := os .WriteFile (concatFile , []byte (concatContent .String ()), 0644 )
663+ err := os .WriteFile (concatFile , []byte (concatContent .String ()), 0o600 )
662664 if err != nil {
663665 return "" , fmt .Errorf ("failed to write concat file: %w" , err )
664666 }
665667 return concatFile , nil
666668}
667669
668670// concatenateAudioFiles uses ffmpeg to concatenate audio files into a single output file
669- func concatenateAudioFiles (concatFile string , outputFile string ) error {
671+ func concatenateAudioFiles (concatFile , outputFile string ) error {
670672 fmt .Println ("Concatenating audio files..." )
671673 concatStartTime := time .Now ()
672674
@@ -680,6 +682,7 @@ func concatenateAudioFiles(concatFile string, outputFile string) error {
680682 outputFile ,
681683 }
682684
685+ // #nosec G204 -- Arguments are constructed internally, not from external input
683686 cmd := exec .Command ("ffmpeg" , args ... )
684687 cmd .Stdout = os .Stdout
685688 cmd .Stderr = os .Stderr
@@ -784,7 +787,7 @@ func generateAndPlayLocally(discussion Discussion, config Config) error {
784787 // Create a temporary file for the audio
785788 filename := fmt .Sprintf ("%s/segment_%03d.mp3" , tempDir , playedIndex )
786789 fmt .Printf ("Writing segment %d to file %s...\n " , playedIndex , filename )
787- err := os .WriteFile (filename , nextSegment .AudioData , 0644 )
790+ err := os .WriteFile (filename , nextSegment .AudioData , 0o600 )
788791 if err != nil {
789792 fmt .Printf ("Error writing segment %d: %v\n " , playedIndex , err )
790793 close (stopChan )
@@ -863,8 +866,10 @@ func playAudioFile(filename string) error {
863866 for _ , player := range players {
864867 if _ , err := exec .LookPath (player ); err == nil {
865868 if player == "aplay" {
869+ // #nosec G204 -- Player is selected from a whitelist of known audio players
866870 cmd = exec .Command (player , "-q" , filename )
867871 } else {
872+ // #nosec G204 -- Player is selected from a whitelist of known audio players
868873 cmd = exec .Command (player , filename , "-nodisp" , "-autoexit" , "-really-quiet" )
869874 }
870875 break
@@ -1016,7 +1021,7 @@ func callOpenAITTSAPI(request OpenAITTSRequest, apiKey string) ([]byte, error) {
10161021}
10171022
10181023// generateOpenAITTS uses OpenAI's Chat Completions API to generate natural speech
1019- func generateOpenAITTS (text , lang , gender , voice string , speed float64 , apiKey string ) ([]byte , error ) {
1024+ func generateOpenAITTS (text , _ /* gender */ , voice string , _ /* speed */ float64 , apiKey string ) ([]byte , error ) {
10201025 // Get the appropriate speaking style for this voice
10211026 speakingStyle := getSpeakingStyle (voice )
10221027
@@ -1058,4 +1063,4 @@ func truncateString(s string, maxLength int) string {
10581063 }
10591064
10601065 return s [:maxLength ] + "..."
1061- }
1066+ }
0 commit comments