|
4 | 4 | "bytes" |
5 | 5 | "encoding/base64" |
6 | 6 | "fmt" |
| 7 | + "io" |
7 | 8 | "log" |
| 9 | + "net/http" |
8 | 10 | "os" |
9 | 11 | "path/filepath" |
10 | 12 | "strings" |
@@ -176,25 +178,21 @@ func attachmentsFromMetadataOnly(metadata map[string]interface{}) (attachments [ |
176 | 178 | return nil |
177 | 179 | } |
178 | 180 | if urls, exists := metadata[actions.MetadataUrls]; exists { |
179 | | - if sl, ok := urls.([]string); ok { |
180 | | - for _, url := range xstrings.UniqueSlice(sl) { |
181 | | - attachments = append(attachments, slack.Attachment{ |
182 | | - Title: "URL", |
183 | | - TitleLink: url, |
184 | | - Text: url, |
185 | | - }) |
186 | | - } |
| 181 | + for _, url := range xstrings.UniqueSlice(stringSliceFromMetadata(urls)) { |
| 182 | + attachments = append(attachments, slack.Attachment{ |
| 183 | + Title: "URL", |
| 184 | + TitleLink: url, |
| 185 | + Text: url, |
| 186 | + }) |
187 | 187 | } |
188 | 188 | } |
189 | 189 | if imagesUrls, exists := metadata[actions.MetadataImages]; exists { |
190 | | - if sl, ok := imagesUrls.([]string); ok { |
191 | | - for _, url := range xstrings.UniqueSlice(sl) { |
192 | | - attachments = append(attachments, slack.Attachment{ |
193 | | - Title: "Image", |
194 | | - TitleLink: url, |
195 | | - ImageURL: url, |
196 | | - }) |
197 | | - } |
| 190 | + for _, url := range xstrings.UniqueSlice(stringSliceFromMetadata(imagesUrls)) { |
| 191 | + attachments = append(attachments, slack.Attachment{ |
| 192 | + Title: "Image", |
| 193 | + TitleLink: url, |
| 194 | + ImageURL: url, |
| 195 | + }) |
198 | 196 | } |
199 | 197 | } |
200 | 198 | return attachments |
@@ -295,6 +293,39 @@ func uploadFilesFromMetadata(metadata map[string]interface{}, api *slack.Client, |
295 | 293 | } |
296 | 294 | } |
297 | 295 | } |
| 296 | + // Handle generated images (download from URL and upload as file, so temporary URLs like DALL-E are preserved) |
| 297 | + if imageUrls, exists := metadata[actions.MetadataImages]; exists { |
| 298 | + sl := stringSliceFromMetadata(imageUrls) |
| 299 | + for _, imgURL := range xstrings.UniqueSlice(sl) { |
| 300 | + resp, err := http.Get(imgURL) |
| 301 | + if err != nil { |
| 302 | + xlog.Error("Error downloading image for Slack upload", "url", imgURL, "error", err) |
| 303 | + continue |
| 304 | + } |
| 305 | + data, err := io.ReadAll(resp.Body) |
| 306 | + resp.Body.Close() |
| 307 | + if err != nil { |
| 308 | + xlog.Error("Error reading image body for Slack upload", "url", imgURL, "error", err) |
| 309 | + continue |
| 310 | + } |
| 311 | + if len(data) == 0 { |
| 312 | + xlog.Error("Empty image body for Slack upload", "url", imgURL) |
| 313 | + continue |
| 314 | + } |
| 315 | + _, err = api.UploadFileV2(slack.UploadFileV2Parameters{ |
| 316 | + Reader: bytes.NewReader(data), |
| 317 | + FileSize: len(data), |
| 318 | + ThreadTimestamp: threadTs, |
| 319 | + Channel: channelID, |
| 320 | + Filename: "image.png", |
| 321 | + Title: "Generated image", |
| 322 | + InitialComment: "Generated image", |
| 323 | + }) |
| 324 | + if err != nil { |
| 325 | + xlog.Error("Slack UploadFileV2 failed for image", "error", err, "url", imgURL) |
| 326 | + } |
| 327 | + } |
| 328 | + } |
298 | 329 | } |
299 | 330 |
|
300 | 331 | // attachmentsAndUploadsFromMetadata returns link/image attachments and uploads files (songs, PDFs) |
@@ -329,16 +360,6 @@ func uploadJobResultFiles(res *types.JobResult, api *slack.Client, channelID, th |
329 | 360 | } |
330 | 361 | } |
331 | 362 |
|
332 | | -func generateAttachmentsFromJobResponse(j *types.JobResult, api *slack.Client, channelID, ts string) (attachments []slack.Attachment) { |
333 | | - if j == nil { |
334 | | - return nil |
335 | | - } |
336 | | - for _, state := range j.State { |
337 | | - attachments = append(attachments, attachmentsAndUploadsFromMetadata(state.Metadata, api, channelID, ts)...) |
338 | | - } |
339 | | - return attachments |
340 | | -} |
341 | | - |
342 | 363 | // ImageData represents a single image with its metadata |
343 | 364 | type ImageData struct { |
344 | 365 | Data []byte |
@@ -641,7 +662,9 @@ func replyWithPostMessage(finalResponse string, api *slack.Client, ev *slackeven |
641 | 662 |
|
642 | 663 | func replyToUpdateMessage(finalResponse string, api *slack.Client, ev *slackevents.AppMentionEvent, msgTs string, ts string, postMessageParams slack.PostMessageParameters, res *types.JobResult) { |
643 | 664 | attachments := attachmentsFromJobResponseOnly(res) |
644 | | - uploadJobResultFiles(res, api, ev.Channel, msgTs) |
| 665 | + // Use the thread root timestamp (ts), not the placeholder reply timestamp (msgTs). |
| 666 | + // Slack API: "Never use a reply's ts value; use its parent instead." |
| 667 | + uploadJobResultFiles(res, api, ev.Channel, ts) |
645 | 668 | if len(finalResponse) > 3000 { |
646 | 669 | messages := xstrings.SplitParagraph(finalResponse, 3000) |
647 | 670 |
|
|
0 commit comments