Uploading a local image using the API

Hey folks,

I hope this is the right place to ask. I’m working on importing a set of data from another forum into our new Discourse instance (internal knowledge posts that we want to transition).
I’ve got our old data nicely packed up in a bunch of JSON structures, but I’m getting hung up on uploading the various images that are related to the old posts.

I’m using the discourse_api gem and have had terrific success so far, though I realize that it’s incomplete. I can upload an image successfully if that image is hosted somewhere, like so:

client.upload_file({'user_id':24,url:'http://valid.url.here/foo.jpg','synchronous':true})

but I’m stymied when trying to upload an image that’s located on my local machine:

client.upload_file({'user_id':24,file:'/local/path/to/image/foo.jpg','synchronous':true})

…the above comes back with:

DiscourseApi::UnprocessableEntity: {"failed"=>"FAILED", 
"message"=>"undefined method 'tempfile' for 
#<String:0x00007f409d2fa398>"} from /home/user/.rvm/gems/
ruby-2.3.7/gems/discourse_api-0.36.0/lib/discourse_api/
client.rb:154:in 'handle_error'

I’ve poked around enough to realize that I probably need to do some base64 magic or something so that my request is properly formed (I’m also realizing that maybe the upload method for the ruby gem just can’t accommodate this type of upload (and if so, that’s fine).

Mostly I’m just looking for guidance, to see if anyone else has come up against this and to see if I’m barking up the wrong tree entirely. Thanks in advance, and all that :slight_smile:

3 Likes

Unless there is some reason you cannot, I recommend looking at the import script directory to see importers that use json files. It’ll be easier than the API.

2 Likes

Hi Tony,

I agree uploading local files can be a bit tricky to figure out using the API. This should do the trick:

# upload_image.rb
require 'discourse_api'
require 'fileutils'

client = DiscourseApi::Client.new("Discourse Site URL")
client.api_key = "Your api key"
client.api_username = "Username"

filename = ARGV[1] #full path to file /home/tony/mypic.png

args = {
  :file => Faraday::UploadIO.new(filename, 'image/png')
}

resp = client.upload_file(args)

url = resp['url']
width = resp['width']
height = resp['height']



# topic_id is the the id for the topic you want to upload the image to.
args = {
  :topic_id => ARGV[0],
  :raw => "<img src=\"#{url}\" width=\"#{width}\" height=\"#{height}\">"
}

# This will create a new post in the specified topic
resp = client.create_post(args)

This will use the Faraday gem to provide the correct file requirements for upload_file. Once the file is uploaded, it then needs to be assigned to a topic/post to show up or it will be auto deleted.

You can call this script with

ruby upload_image.rb <topic_id> <filename>
ruby upload_image.rb 128 /home/tony/mypic.png
3 Likes

Blake,

Thanks a million. I’m going to go read up on the Faraday gem so that I know what it’s doing for me, and test out your script. I really appreciate you taking the time!

-tony.

2 Likes

I spent quite some time trying to figure out how to invoke the upload endpoint from a Python script. I first followed the recommendation to reverse engineer the upload API call through Chrome dev tools. Next I replicated the upload call via Postman. Time consuming part for me was to translate that to a working Python code. For some reason, the Postman-generated Python code wasn’t working either. Here’s how it finally worked for me:

requests.post('/uploads.json', files={'files[]': open('/path/to/image.png', 'rb')}, data={'type': 'composer'})

3 Likes