Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to create media attachment from Buffer #481

Closed
lostfictions opened this issue Sep 4, 2021 · 10 comments · Fixed by #631 or #748
Closed

Unable to create media attachment from Buffer #481

lostfictions opened this issue Sep 4, 2021 · 10 comments · Fixed by #631 or #748
Labels
bug Something isn't working help wanted Extra attention is needed released

Comments

@lostfictions
Copy link
Contributor

lostfictions commented Sep 4, 2021

Hi again, thanks for all your work on this library!

I seem to be getting odd errors when creating media attachments from a Buffer. This works:

const { id } = await masto.mediaAttachments.create({
  file: createReadStream("test.jpg")
});

however, this:

const { id } = await masto.mediaAttachments.create({
  file: readFileSync("test.jpg")
});

fails with this output:

undefined:1

SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at SerializerImpl.deserialize (<repo>/node_modules/masto/dist/index.js:3173:43)
    at transformResponse (<repo>/node_modules/masto/dist/index.js:3295:41)
    at transform (<repo>/node_modules/axios/lib/core/transformData.js:16:12)
    at Object.forEach (<repo>/node_modules/axios/lib/utils.js:247:10)
    at transformData (<repo>/node_modules/axios/lib/core/transformData.js:15:9)
    at onAdapterRejection (<repo>/node_modules/axios/lib/core/dispatchRequest.js:69:32)

form-data (and by extension isomorphic-form-data) should accept Buffers, as noted in their readmes. The error seems like it might be in masto.js code?

It's not a big deal for the above example (createReadStream is probably preferable to reading the whole file in from memory anyway) but for other cases (for example, creating image data in-memory using node-canvas or similar) it's a bit inconvenient to have to write a file to a temporary location and then read it back rather than using a Buffer directly.


Edit: looked into this a bit -- it seems like the deserializer is trying to JSON.parse an empty string (length === 0). If I make the deserializer return an empty object in this case, I get this output instead:

node_modules/masto/dist/index.js:199
        var _this = _super.call(this) || this;
                           ^

Error: Unexpected error occurred
    at new MastoError (<repo>/node_modules/masto/dist/index.js:199:28)
    at HttpAxiosImpl.<anonymous> (<repo>/node_modules/masto/dist/index.js:3345:39)
    at step (<repo>/node_modules/masto/dist/index.js:130:23)
    at Object.throw (<repo>/node_modules/masto/dist/index.js:111:53)
    at rejected (<repo>/node_modules/masto/dist/index.js:102:65)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  statusCode: undefined,
  description: undefined,
  details: undefined,
  isMastoError: true
}

If I add logging here, I can finally see that this is a status code 400. (It might be nice to add the status code to the default case and the request URL too, it's kinda confusing without it and makes it seem like an internal error...)

I'm still not sure why using a Buffer isn't working here, but I'll keep investigating.

@neet
Copy link
Owner

neet commented Sep 5, 2021

Hi, thanks again for the report.

form-data (and by extension isomorphic-form-data) should accept Buffers, as noted in their readmes. The error seems like it might be in masto.js code?

This might be correct. I read the form-data doc again and realized that this is probably because we don't put form.getBuffer thing on the request. It seems to be required to put it if we use form-data with Axios. And because of this, generated form-data request payload comes to be invalid so Mastodon instance just returns 400 error.

c.f. https://github.com/form-data/form-data#buffer-getbuffer

Also, sorry for the inconvenience of de-serializer / 400 error issues. I'd also like to fix the empty string problem and more understandable error message.

@neet neet added bug Something isn't working help wanted Extra attention is needed labels Sep 5, 2021
@neet
Copy link
Owner

neet commented Sep 10, 2021

I tried a raw Axios request with form-data as it is documented in the form data's README. However, I ended up with the same error as above, so this might be either a Mastodon's bug or form data's bug, I guess.

const axios = require('axios');
const fs = require('fs');
const path = require('path');
const FormData = require('form-data')

const form = new FormData();

form.append(
  'file',
  fs.readFileSync(path.join(__dirname, './some_image.jpg')),
);

const media = await axios.post(
  `https://mastodon.social/api/v2/media`,
  form.getBuffer(),
  { headers: { ...form.getHeaders(), Authorization: `Bearer ${process.env.MASTODON_TOKEN}` } },
)

result:

I also thought converting the Buffer into a ReadableStream may help evade this issue but in this case, I got an error from the mastodon instance... I'll keep researching the solution

Screen Shot 2021-09-10 at 22 40 47


Regarding the lacking of 400 information and unhandled JSON.parse exception, I fixed it in the pull request #484

@flleeppyy
Copy link

flleeppyy commented Feb 22, 2022

bump

Still getting 400 responses
image

And the error's do not provide the Axios request object, which they should...

@flleeppyy
Copy link

mastodon/mastodon#17622

@neet
Copy link
Owner

neet commented Jun 19, 2022

Here's an update from #631 #626 . Even though I still could not figure out which component (including form-data, Axios, or Mastodon itself) was causing this bug, I tried out the Node.js-native implementation of Fetch API and everything worked OK.

Node.js's fetch API has just been introduced at v18.X.X so I still have to maintain the Axios version for a while, but I'm going to drop the Axios version in the future. If you are still in trouble or want to try out the new version, you can install the Fetch API version with the following command:

yarn add masto@^4.6.0

And change your import to following

- import { login } from 'masto';
+ import { login } from 'masto/fetch';

Additionally, since it's Fetch API, you need to convert the buffer to a blob. you can basically do that just by passing the buffer to the blob constructor.

image

@neet neet linked a pull request Jun 19, 2022 that will close this issue
@neet neet mentioned this issue Dec 2, 2022
@eramdam
Copy link

eramdam commented Dec 18, 2022

@neet thank you for the workaround! Is there a way to get typings when importing masto/fetch? TypeScript complains that there aren't any and I'd rather not disable all typings with // @ts-expect-error :)

eramdam added a commit to eramdam/shapeshifter-themes that referenced this issue Dec 18, 2022
@neet
Copy link
Owner

neet commented Dec 18, 2022

@eramdam You would need to change tsconfig.json as follows since the sub-package is served by a feature called package exports which was introduced in Node.js 16+.

{
  "compilerOptions": {
-   "moduleResolution": "node",
+   "moduleResolution": "node16",
}

Or if you want to stay in "moduleResolution": node for some reason, you can create the following declaration file to your src/global.d.ts or anywhere you want.

declare module 'masto/fetch' {
  export * from 'masto';
}

Update: @danielroe has added backward compatibility to the masto/fetch's type definition in #761 so you don't need to update compilerOptions.moduleResoltuion.

@neet neet added the v5 label Dec 22, 2022
@neet neet mentioned this issue Dec 23, 2022
@neet neet closed this as completed in #748 Dec 23, 2022
@github-actions
Copy link

🎉 This issue has been resolved in version 5.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@gmag11
Copy link

gmag11 commented Jan 31, 2024

Hi. I was trying to investigate this. I am using this in NodeRed and have a picture in a binary buffer. Trying to send the picture gives an error ("Cannot process thumbnail creation). My conclusion is that current implementation does not send content type header. This is mandatory to upload a file to Mastodon server.

This is the code that finally worked for me:

async function uploadMedia(rawContent, type) {
    if (Buffer.isBuffer(rawContent) && typeof type === 'string') {
        const form = new FormData();
        form.append('file', rawContent, {
            filename: 'myfile.jpg', // Replace with your file's name
            contentType: type
        });

        const params = { 
            file: form.getBuffer(),
        };

        const msg1 = {
            payload: form
        };
        //const response = await masto.v2.media.create(params);
        const response = axios.post(`${url}/api/v2/media`,
            form.getBuffer(),
            { headers: { ...form.getHeaders(), Authorization: `Bearer ${token}` } }
        )
        return response;
    } else {
        node.error("Mastodon upload media error");
    }
}

@neet
Copy link
Owner

neet commented Feb 5, 2024

Hi @gmag11, thank you for sharing your tips. As of 5.0.0 (and 6.0.0 as well), we have dropped the support for Axios, replacing it with Node.js native Fetch API, so I believe now you can upload media regardless it is a file or a raw buffer input.

The following code describes how to upload media created using npm canvas.

import { Canvas } from "canvas";

function createCircle() {
  const width = 100;
  const height = 100;

  const canvas = new Canvas(width, height);
  const context = canvas.getContext("2d");

  context.fillStyle = "red";
  context.beginPath();
  context.arc(width / 2, height / 2, width / 2, 0, 2 * Math.PI);
  context.fill();

  return canvas.toBuffer("image/png");
}

async function main() {
  // 🚨 You need to wrap buffer by `Blob`
  const file = new Blob([createCircle()]);

  const media = await masto.v2.media.create({
    file,
    description: "Test upload",
  });

  await masto.v1.statuses.create({
    status: "Hello, world!",
    visibility: "private",
    mediaIds: [media.id],
  });
}

Let me know if you still have a problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed released
Projects
None yet
5 participants