slackapi / python-slack-sdk

Slack Developer Kit for Python
https://tools.slack.dev/python-slack-sdk/
MIT License
3.86k stars 834 forks source link

Cannot upload file to Channel using Api #1575

Open nobikin95 opened 4 weeks ago

nobikin95 commented 4 weeks ago

I am trying to upload a file to a private Slack channel using the Slack API via the following Python script. However, once uploaded, the file cannot be downloaded by users in the channel. I am looking for help to understand why this is happening and how to resolve it.

Python runtime version

3.12.1

I used these step:

  1. https://slack.com/api/files.getUploadURLExternal
  2. https://slack.com/api/files.completeUploadExternal
  3. https://slack.com/api/files.sharedPublicURL

Actual result:

{
  "ok": true,
  "file": {
    "id": "XXXXXXXXXX",
    "created": 1729848820,
    "timestamp": 1729848820,
    "name": "xxxxxxxxxxxxxxxxxx.xxx",
    "title": "xxxxxxxxxxxxxxxxxxxx.xxx",
    "mimetype": "",
    "filetype": "",
    "pretty_type": "",
    "user": "XXXXXXXXX",
    "user_team": "XXXXXXXX",
    "editable": false,
    "size": 61652577,
    "mode": "hosted",
    "is_external": false,
    "external_type": "",
    "is_public": false,
    "public_url_shared": true,
    "display_as_bot": false,
    "username": "",
    "url_private": "https://files.slack.com/files-pri/xxxxxxxxx-xxxxxxxxxx/xxxxxxxxxxxxxxxxxx.xxx",
    "url_private_download": "https://files.slack.com/files-pri/xxxxxxxxxxxxx-xxxxxxx/download/xxxxxxxxxxxxxxxxxx.xxx",
    "media_display_type": "unknown",
    "permalink": "https://xxxxxxxxx.slack.com/files/xxxxxxxxxx/xxxxxxxxxxxxxxx/xxxxxxxxxxxxx.xxx",
    "permalink_public": "https://slack-files.com/xxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxx",
    "comments_count": 0,
    "is_starred": false,
    "shares": {},
    "channels": [],
    "groups": [],
    "ims": [],
    "has_more_shares": false,
    "has_rich_preview": false,
    "file_access": "visible"
  }
}
srajiang commented 4 weeks ago

Hi @nobikin95 - I noticed that the successful output has is_public: false, and the channels array is empty. What was your output for the files.completeUploadExternal request? I just want to check here that you supplied the channel_id, otherwise the file will be private.

nobikin95 commented 3 weeks ago

Hi @srajiang, output for the files.completeUploadExternal :

  "ok": true,
  "files": [
    {
      "id": "xxxxxxx",
      "created": 1729847794,
      "timestamp": 1729847794,
      "name": "xxxxxxxxxxxxx.xxx",
      "title": "xxxxxxxxxxx.xxx",
      "mimetype": "",
      "filetype": "",
      "pretty_type": "",
      "user": "xxxxxxxxx",
      "user_team": "xxxxxxxxx",
      "editable": false,
      "size": 61652512,
      "mode": "hosted",
      "is_external": false,
      "external_type": "",
      "is_public": false,
      "public_url_shared": false,
      "display_as_bot": false,
      "username": "",
      "url_private": "https://files.slack.com/files-pri/XXXXXXXXXXXXX-XXXXXXXXXXX/xxxxxxxxxxxxxxxxx.xxx",
      "url_private_download": "https://files.slack.com/files-pri/XXXXXXXXXXXXX-XXXXXXXXXXX/download/xxxxxxxxxxxxxxxxx.xxx",
      "media_display_type": "unknown",
      "permalink": "https://ikameglobal.slack.com/files/XXXXXXXXXXXXX/XXXXXXXXXXXXX/xxxxxxxxxxxxxxxxx.xxx",
      "permalink_public": "https://slack-files.com/XXXXXXXXXXXXX-XXXXXXXXXXXXX-xxxxxxxxxx",
      "comments_count": 0,
      "is_starred": false,
      "shares": {},
      "channels": [],
      "groups": [],
      "ims": [],
      "has_more_shares": false,
      "has_rich_preview": false,
      "file_access": "visible"
    }
  ]
}
nobikin95 commented 3 weeks ago

It seems like I'm having an issue with channel_id, here is the code I'm using:

#!/bin/bash

FILE_PATH=$1
CHANNEL_ID="XXXXXXXXXXX"
USER_TOKEN="xoxp-"  
BOT_TOKEN="xoxb-"   

# Check if the file exists
echo "Checking if file exists at: $FILE_PATH"
if [ ! -f "$FILE_PATH" ]; then
    echo "Error: File $FILE_PATH does not exist!"
    exit 1
fi
echo "File found at: $FILE_PATH"

# Get file size (use command compatible with macOS and Linux)
if [[ "$OSTYPE" == "darwin"* ]]; then
    FILE_SIZE=$(stat -f%z "$FILE_PATH")
else
    FILE_SIZE=$(stat -c%s "$FILE_PATH")
fi

# Step 1: Get upload URL using the files.getUploadURLExternal API
echo "Fetching upload URL from Slack API..."
UPLOAD_URL_RESPONSE=$(curl -s -X POST \
    -H "Authorization: Bearer $USER_TOKEN" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data "filename=$(basename "$FILE_PATH")&length=$FILE_SIZE&channels=$CHANNEL_ID" \
    https://slack.com/api/files.getUploadURLExternal)

# Log the response from the API
echo "Response from files.getUploadURLExternal: $UPLOAD_URL_RESPONSE"

# Extract upload URL and file_id from JSON response
UPLOAD_URL=$(echo "$UPLOAD_URL_RESPONSE" | grep -o '"upload_url":"[^"]*' | sed 's/"upload_url":"//g' | sed 's/\\//g')
FILE_ID=$(echo "$UPLOAD_URL_RESPONSE" | grep -o '"file_id":"[^"]*' | sed 's/"file_id":"//g')

# Check if the URL or file_id was not obtained
if [ -z "$UPLOAD_URL" ] || [ -z "$FILE_ID" ]; then
    echo "Error: Failed to get upload URL or file_id"
    exit 1
fi
echo "Upload URL: $UPLOAD_URL"
echo "File ID: $FILE_ID"

# Step 2: Upload the file to the obtained URL
echo "Uploading file to: $UPLOAD_URL"
UPLOAD_RESULT=$(curl -s -X PUT \
    -H "Content-Type: application/octet-stream" \
    --data-binary @"$FILE_PATH" \
    "$UPLOAD_URL")

# Log the upload result
echo "Result of file upload: $UPLOAD_RESULT"

# Step 3: Complete the upload using the files.completeUploadExternal API
echo "Completing file upload with Slack API..."
COMPLETE_RESPONSE=$(curl -s -X POST \
    -H "Authorization: Bearer $USER_TOKEN" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data-urlencode "files=[{\"id\":\"$FILE_ID\"}]" \
    --data-urlencode "channels=$CHANNEL_ID" \
    https://slack.com/api/files.completeUploadExternal)

# Log the response from the files.completeUploadExternal API
echo "Response from files.completeUploadExternal: $COMPLETE_RESPONSE"

# Step 3.1: Share the public URL using the files.sharedPublicURL API
echo "Sharing file publicly using Slack API..."
SHARED_PUBLIC_URL_RESPONSE=$(curl -s -X POST \
    -H "Authorization: Bearer $USER_TOKEN" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data "file=$FILE_ID" \
    https://slack.com/api/files.sharedPublicURL)

# Log the response from the files.sharedPublicURL API
echo "Response from files.sharedPublicURL: $SHARED_PUBLIC_URL_RESPONSE"

# Extract URL from JSON response (use permalink instead of permalink_public)
PUBLIC_URL=$(echo "$SHARED_PUBLIC_URL_RESPONSE" | grep -o '"permalink_public":"[^"]*' | sed 's/"permalink_public":"//g' | sed 's/\\//g')

# Check if the URL was not obtained
if [ -z "$PUBLIC_URL" ]; then
    echo "Error: Failed to get permalink URL"
    exit 1
fi
echo "Public URL: $PUBLIC_URL"

# Step 4: Post a message to the channel with the uploaded file using chat.postMessage API
echo "Posting message to channel with uploaded file ..."
POST_MESSAGE_RESPONSE=$(curl -s -X POST \
    -H "Authorization: Bearer $USER_TOKEN" \
    -H "Content-Type: application/json" \
    --data "$(cat <<EOF
{
    "channel": "$CHANNEL_ID",
    "text": "Build Success: <$PUBLIC_URL>",
    "attachments": [
        {
            "title": "Click here to download",
            "title_link": "$PUBLIC_URL",
            "text": "The file is available for public download."
        }
    ]
}
EOF
    )" \
    https://slack.com/api/chat.postMessage)

# Log the response from the chat.postMessage API
echo "Response from chat.postMessage: $POST_MESSAGE_RESPONSE"
seratch commented 3 weeks ago

The cause of your confusion may not be related to this, but the files.completeUploadExternal API processes its tasks asynchronously. This means that if a subsequent operation attempts to download the uploaded file immediately, it might fail until the files.completeUploadExternal operation completes. It appears that your code is trying to download the files right away. In most cases, this can fail regardless of whether the files are public or private.

One way to determine if the upload operation is finished is to check if the "shared" property is available in the file's metadata. You can verify this by polling the files.info API with the uploaded file's ID.