Skip to content

Commit e3558f9

Browse files
zzstoatzzclaude
andauthored
fix(slackbot): use files.upload for actual Slack snippets (#1263)
The new files.getUploadURLExternal API creates file attachments, not snippets. Switch back to the deprecated files.upload with content parameter which creates proper snippets with line numbers and collapsible UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 08ee978 commit e3558f9

1 file changed

Lines changed: 28 additions & 61 deletions

File tree

examples/slackbot/src/slackbot/slack.py

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ async def create_progress_message(
367367
return progress
368368

369369

370-
# --- Slack File Upload API (files.getUploadURLExternal + completeUploadExternal) ---
370+
# --- Slack Snippet Upload (files.upload with content parameter) ---
371371

372372

373373
async def upload_snippet(
@@ -378,12 +378,14 @@ async def upload_snippet(
378378
title: str | None = None,
379379
filetype: str | None = None,
380380
) -> dict[str, Any]:
381-
"""Upload a code snippet as a Slack file using the new external upload API.
381+
"""Upload a code snippet using files.upload with content parameter.
382382
383-
Uses the three-step process:
384-
1. files.getUploadURLExternal - get upload URL and file ID
385-
2. POST content to that URL
386-
3. files.completeUploadExternal - finalize and share in channel
383+
This creates an actual Slack snippet (with line numbers and collapsible UI),
384+
not just a file attachment. Uses the `content` parameter which creates
385+
editable snippets.
386+
387+
Note: files.upload is deprecated but still works. The new external upload API
388+
(files.getUploadURLExternal) doesn't create proper snippets with line numbers.
387389
388390
Args:
389391
content: The snippet content to upload
@@ -396,70 +398,35 @@ async def upload_snippet(
396398
Returns:
397399
dict with file info from Slack API
398400
"""
399-
content_bytes = content.encode("utf-8")
400-
length = len(content_bytes)
401+
# Build form data for multipart upload
402+
# Using `content` parameter (not `file`) creates an editable snippet
403+
form_data: dict[str, Any] = {
404+
"content": content,
405+
"filename": filename,
406+
"channels": channel_id,
407+
}
408+
if title:
409+
form_data["title"] = title
410+
if filetype:
411+
form_data["filetype"] = filetype
412+
if thread_ts:
413+
form_data["thread_ts"] = thread_ts
401414

402415
async with httpx.AsyncClient() as client:
403-
# Step 1: Get upload URL
404-
get_url_params: dict[str, Any] = {
405-
"filename": filename,
406-
"length": length,
407-
}
408-
if filetype:
409-
get_url_params["snippet_type"] = filetype
410-
411-
response = await client.get(
412-
"https://slack.com/api/files.getUploadURLExternal",
416+
response = await client.post(
417+
"https://slack.com/api/files.upload",
413418
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
414-
params=get_url_params,
419+
data=form_data,
415420
)
416421
response.raise_for_status()
417-
url_data = response.json()
418-
419-
if not url_data.get("ok"):
420-
raise ValueError(
421-
f"Failed to get upload URL: {url_data.get('error', 'unknown error')}"
422-
)
423-
424-
upload_url = url_data["upload_url"]
425-
file_id = url_data["file_id"]
426-
427-
# Step 2: Upload content to the provided URL
428-
upload_response = await client.post(
429-
upload_url,
430-
content=content_bytes,
431-
headers={"Content-Type": "application/octet-stream"},
432-
)
433-
if upload_response.status_code != 200:
434-
raise ValueError(
435-
f"Failed to upload file content: {upload_response.status_code}"
436-
)
437-
438-
# Step 3: Complete the upload and share in channel
439-
complete_payload: dict[str, Any] = {
440-
"files": [{"id": file_id, "title": title or filename}],
441-
"channel_id": channel_id,
442-
}
443-
if thread_ts:
444-
complete_payload["thread_ts"] = thread_ts
445-
446-
complete_response = await client.post(
447-
"https://slack.com/api/files.completeUploadExternal",
448-
headers={
449-
"Authorization": f"Bearer {settings.slack_api_token}",
450-
"Content-Type": "application/json",
451-
},
452-
json=complete_payload,
453-
)
454-
complete_response.raise_for_status()
455-
complete_data = complete_response.json()
422+
result = response.json()
456423

457-
if not complete_data.get("ok"):
424+
if not result.get("ok"):
458425
raise ValueError(
459-
f"Failed to complete upload: {complete_data.get('error', 'unknown error')}"
426+
f"Failed to upload snippet: {result.get('error', 'unknown error')}"
460427
)
461428

462-
return complete_data
429+
return result
463430

464431

465432
# Language to file extension mapping for snippet filenames

0 commit comments

Comments
 (0)