Search code examples
pythonapibasecamp

Posting images to Basecamp Campfire using API with Python


I'm new with Python and APIs and I'm trying to upload an image into Basecamp campfire by using Basecamp 3 API. I have achieved authentication with my Basecamp account and was able to post a text message to campfire with this script, but I'm having trouble with images/files, and I couldn't find any examples of how to do this. I posted the image file to '/attachments.json' and gathered its attachable_sgid, but I think I'm not using it correctly.

Here's a link to Basecamp 3 API documentation.

Here's the script that I created:

import requests

access_token = 'token here'

account_id = "5437964"
project_id = "29141823"
campfire_id = "5288317671"

base_url = f"https://3.basecampapi.com/{account_id}"

# Post request to acquire attachable_sgid of an image from my filesystem https://github.com/basecamp/bc3-api/blob/master/sections/attachments.md#attachments

attachments_url = f"{base_url}/attachments.json?name=img.png"
img_headers = headers = {
    'Authorization': 'Bearer '+ access_token,
    "Content-Type": "image/png",
    "Content-Length": "123"
    }

with open("img.png", "rb") as img_content:
    image_id = requests.post(attachments_url, headers=img_headers, data=img_content).json()['attachable_sgid']

# Uploading image to Basecamp campfire

headers = {
    "Authorization": "Bearer " + access_token,
    "Content-Type": "application/json",
}


img_data = '{"content": "' + image_id + '"}'

campfire_url = f"{base_url}/buckets/{project_id}/chats/{campfire_id}/lines.json"

requests.post(campfire_url, headers=headers, data=img_data)

I'm getting the following message {'status': 400, 'error': 'Bad Request'}.

Does anyone know how to correctly upload the image in this case?


Solution

  • I found the answer to this. First, the image sgid needs to be inside "bc-attachment" html tag like this <bc-attachment sgid='sgid here'></bc-attachment>.

    Second problem is that at the moment of writing this Basecamp 3 API does not support posting rich text to campfires (even though campfires can have rich text within Basecamp, API is not able to do this), and attachments are considered as rich text.

    So instead I have chosen to do this by attaching images to the message board and here is how I did it:

    import requests
    
    access_token = 'token here'
    
    account_id = "5437964"
    project_id = "29141823"
    message_board_id = "5288317664"
    
    base_url = f"https://3.basecampapi.com/{account_id}"
    
    # Post request to acquire attachable_sgid of an image from my filesystem https://github.com/basecamp/bc3-api/blob/master/sections/attachments.md#attachments
    
    attachments_url = f"{base_url}/attachments.json?name=img.png"
    img_headers = headers = {
        'Authorization': 'Bearer '+ access_token,
        "Content-Type": "image/png",
        "Content-Length": "123"
        }
    
    with open("img.png", "rb") as img_content:
        image_id = requests.post(attachments_url, headers=img_headers, data=img_content).json()['attachable_sgid']
    
    # Uploading image to Basecamp message board
    
    headers = {
        "Authorization": "Bearer " + access_token,
        "Content-Type": "application/json",
    }
    
    data = f"<bc-attachment sgid='{image_id}'></bc-attachment>"
    
    url = f"{base_url}/buckets/{project_id}/message_boards/{message_board_id}/messages.json"
    
    requests.post(url, headers=headers, data='{"subject": "hello", "content": "<div>'+data+'</div>", "status": "active"}')
    

    Edit: Adding code snippet for retrieving a new access token due to request in comments.

    def get_access_token():
        client_id = keys['basecamp_client_id']
        client_secret = keys['basecamp_client_secret']
        redirect_url = keys['basecamp_redirect_uri']
        refresh_token = keys['basecamp_refresh_token']
    
        refresh_token_url = f"https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token={refresh_token}&client_id={client_id}&redirect_uri={redirect_url}&client_secret={client_secret}"
        access_token = requests.post(refresh_token_url).json()['access_token']
        return access_token
    

    You can further optimize this to only request a new access token once the old one has expired.

    Edit 2: I created a python package that simplifies the interaction with Basecamp API - basecampapi