knadh / listmonk

High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.
https://listmonk.app
GNU Affero General Public License v3.0
14.73k stars 1.35k forks source link

Upload media via REST API #770

Closed NicoHood closed 2 years ago

NicoHood commented 2 years ago

I try to upload media files using the REST API: https://listmonk.app/docs/apis/media/

The API and the example says I must use multipart/form-data but I am new to this. Can you maybe go more into detail how to upload a file? Especially I want to upload the file via python.

For reference, this is how I do it with the wordpress rest api:

headers = {
    "Content-Type": mimetypes.guess_type(filename)[0],
    "Accept": "application/json",
    "Content-Disposition": f"attachment; filename={filename}",
}

Also: How do I search for existing media files? It does not seem this is documented. The get curl example also contains some multipart/form-data, I am not sure if that is correct.

NicoHood commented 2 years ago

I found some more python samples, but still not working:

        url = f"{self.base_url}/api/media"

        headers = {
            # "Content-Type": 'multipart/form-data;boundary="boundary"',
            "Accept": "application/json"
            # "Content-Disposition": f"attachment; filename={filename}",
        }

        logging.info(f"Uploading media to {url}")
        # https://2.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file
        # https://2.python-requests.org/en/master/user/advanced/#multipart
        files = { "foo": ("filename.csv", 'testcontent', mimetypes.guess_type(filename)[0]) }
        response = requests.post(url, headers=headers, auth=self.__auth, files=files)
        if response.status_code != 201:
            logging.critical(f"Error uploading media file: {response.status_code} {response.text}")
            sys.exit(1)
# --> CRITICAL:root:Error uploading media file: 400 {"message":"Ungültige Datei: http: no such file"}
NicoHood commented 2 years ago

I was able to get it working:

    def upload_media_file(self, image: BinaryIO, filename: str) -> None:
        url = f"{self.base_url}/api/media"

        logging.info(f"Uploading media to {url}")
        # https://2.python-requests.org/en/master/user/quickstart/#post-a-multipart-encoded-file
        # https://2.python-requests.org/en/master/user/advanced/#multipart
        files = { "file": (filename, image, mimetypes.guess_type(filename)[0]) }
        response = requests.post(url,  auth=self.__auth, files=files)
        if response.status_code != 200:
            logging.critical(f"Error uploading media file: {response.status_code} {response.text}")
            sys.exit(1)

        data = response.json()
        # print(json.dumps(data, indent=4, sort_keys=True))

        if data["data"] != True:
            logging.critical(f"Error uploading media file (invalid API response): {data}")
            sys.exit(1)

There is no documentation, that the name in this multipart form field should be file and not foo as in my case. I found that looking at the code and try/error.

Now I am wondering why I don't get any information about the uploaded media back. What if it already exists and was renamed? I need the newly generated url or at least an ID to further proceed. Is that possible somehow?

knadh commented 2 years ago

Now I am wondering why I don't get any information about the uploaded media back. What if it already exists and was renamed?

Files with conflicting filenames get a new name, so there are no duplicate file errors or overrides.

I need the newly generated url or at least an ID to further proceed. Is that possible somehow?

You are right. This was missed. Will add the uploaded file's URL in the response.

NicoHood commented 2 years ago

Alright. I would expect the API to return a media object, similar to /api/media. This would be a breaking change, but makes a lot sense to me.

Also a dedicated get for /api/media/id would be nice.

And I need to search for existing media filename to avoid a reupload. Even better would be to even place a sha256 or sha512sum inside the response to check if the file matches without downloading it. The checksum could be added after uploading, as the file is processed anyways.

NicoHood commented 2 years ago

Another addition: The docs say, that the rest api returns a width and a height. But they don't do that for me.

Also thumb_uri and uri were renamed to url https://listmonk.app/docs/apis/media/

The provider is missing in the docs at all.

NicoHood commented 2 years ago

Is it possible to get a media object by id now? Also how about searching for files via filename?

knadh commented 2 years ago

Yes, it's possible. Turns out, it was always possible! /api/media/:id always worked.

Also how about searching for files via filename?

This is a new feature that we can consider adding. Will need a search box in the media UI as well.