JavaScript fetch()Discord文件上载导致HTTP 400“无法发送空消息50006”错误

时间:2020-07-16 05:11:57

标签: javascript file upload discord fetch-api

由于无法使用Discord.js,我正尝试通过使用Webhook的香草javascript将单个PNG文件上传到Discord频道。

在Chrome的JavaScript控制台中,我收到以下错误消息:

POST https://discordapp.com/api/webhooks/ <我的不和谐webhook> 400

console.log()输出之后:

成功:{消息:“无法发送空消息”,代码:50006}

我在最后添加了我的代码和网络控制台记录,但我有1个问题:

  • 我在做什么错?我看到文件的Content-Disposition类型为form-data,与我的问题有任何联系吗?

在此之前,我设法使用XMLHttpRequest和fetch来发布消息(仅json,没有文件)。

尽管我可以并且希望使用纯Javascript解决方案,但我没有尝试使用JQuery。

下面是我编写的代码,用于发送单个PNG图像以及有效负载_json JSON数据有效负载(没有有效负载时会发生相同的错误):

    const filename = 'image.png';
    domtoimage.toBlob(document.querySelector('<element to snapshot selector>'))
      .then(function (blob) {
        const formData = new FormData();
        var params = {
          username: "My bot name",
          avatar_url: "",
          content: "another test 2",
          embeds: [{
            "image": {
              "url": "https://i.imgur.com/ZGPxFN2.jpg"
            }
          }]
        };
        formData.append('payload_json',JSON.stringify(params))
        formData.append('file',blob);
        fetch('https://discordapp.com/api/webhooks/<my discord webhook>', {
          method: 'POST',
          headers: {
            'Content-Type': 'multipart/form-data',
          },
          body: formData,
        })
        .then(response => response.json())
        .then(result => {
          console.log('Success:', result);
        })
        .catch(error => {
          console.error('Error:', error);
        });
      });

在Chrome的网络控制台中,我获得以下信息:

General
    Request URL: https://discordapp.com/api/webhooks/<my discord webhook>
    Request Method: POST
    Status Code: 400 
    Remote Address: 162.159.134.233:443
    Referrer Policy: no-referrer-when-downgrade
Response Headers
    access-control-allow-credentials: true
    access-control-allow-headers: Content-Type, Authorization, X-Track, X-Super-Properties, X-Context-Properties, X-Failed-Requests, X-Fingerprint, X-RPC-Proxy, X-Debug-Options, x-client-trace-id, If-None-Match, X-RateLimit-Precision
    access-control-allow-methods: POST, GET, PUT, PATCH, DELETE
    access-control-allow-origin: <access-control-allow-origin>
    access-control-expose-headers: Retry-After, X-RateLimit-Global, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Bucket, Date
    cf-cache-status: DYNAMIC
    cf-ray: <cf-ray>
    cf-request-id: <cf-request-id>
    content-length: 58
    content-type: application/json
    date: Thu, 16 Jul 2020 04:23:41 GMT
    expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
    server: cloudflare
    set-cookie: __cfduid=<__cfduid>; expires=Sat, 15-Aug-20 04:23:40 GMT; path=/; domain=.discordapp.com; HttpOnly; SameSite=Lax
    set-cookie: __cfruid=<__cfduid>; path=/; domain=.discordapp.com; HttpOnly; Secure; SameSite=None
    status: 400
    strict-transport-security: max-age=31536000; includeSubDomains
    via: 1.1 google
    x-envoy-upstream-service-time: 27
    x-ratelimit-bucket: <x-ratelimit-bucket>
    x-ratelimit-limit: 5
    x-ratelimit-remaining: 4
    x-ratelimit-reset: <x-ratelimit-reset>
    x-ratelimit-reset-after: 2
Request Headers
    :authority: discordapp.com
    :method: POST
    :path: /api/webhooks/<my discord webhook>
    :scheme: https
    accept: */*
    accept-encoding: gzip, deflate, br
    accept-language: en,en-US;q=0.9,ja;q=0.8
    content-length: 50696
    content-type: multipart/form-data
    origin: https://<origin>
    referer: https://<referer>
    sec-fetch-dest: empty
    sec-fetch-mode: cors
    sec-fetch-site: cross-site
    user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Request Payload
    ------WebKitFormBoundarypu7GvABzASKKKTQF
    Content-Disposition: form-data; name="payload_json"

    {"username":"WhiteBoard","avatar_url":"https://i.imgur.com/ZGPxFN2.jpg","content":"another test 2","embeds":[{"image":{"url":"https://i.imgur.com/ZGPxFN2.jpg"}}]}
    ------WebKitFormBoundarypu7GvABzASKKKTQF
    Content-Disposition: form-data; name="file"; filename="blob"
    Content-Type: image/png

    ‰PNG
    

    IHDRpíã IDAT[...]
    [...]:cjIEND®B`‚
    ------WebKitFormBoundarypu7GvABzASKKKTQF--

--------编辑(2020/07/16 14:30):我自己回答第二个问题--------

  • 为什么请求有效负载中的文件名部分为“ blob”? FormData.append()接受文件名的第三个参数,我的代码应如下所示:
    formData.append('file',blob,filename);

1 个答案:

答案 0 :(得分:3)

构建一个简单的 js 函数,在发送到服务器之前在客户端调整图像大小。 我得到了 base64 编码的结果并用这个函数制作了一个 blob

const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];
  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

然后,我将图像作为 blob 发送到带有 fetch 的 discord webhook

function sendMessage(img, obj=false) {
  console.log("send")
  const WH_URL = "YOUR_URL"
  if(!obj)
    obj = {
      content: ''
    }
  var myHeaders = new Headers();
  var formdata = new FormData();
  formdata.append("file", img, "img.png");
  formdata.append("payload_json", JSON.stringify(obj));
  var requestOptions = {
    method: 'POST',
    headers: myHeaders,
    body: formdata,
    redirect: 'follow'
  };
fetch(WH_URL, requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
}

完整示例:

const resizeImage = data =>{
  return new Promise( r =>{
    let dst, ctx, width, start, time, _data;
    let img = new Image();
    img.src = data
    img.onload = () => {
      width = 300;
      dst = document.createElement("canvas")
      ctx = dst.getContext("2d")
      dst.width = width;
      dst.height = img.height * width / img.width
      start = performance.now()
      ctx.drawImage(img, 0, 0, dst.width, dst.height)
      time = (performance.now() - start).toFixed(2)
      _data = dst.toDataURL()
      r(_data)
    }
  })
}
const readImg = async form =>{
  var files = form.files;
  if (files.length === 0)
    return;
  let data = await resizeImage(window.URL.createObjectURL(files[0]))
  document.querySelector('#img').src = data;
  let b64 = data.split(",")[1] // get only base64
  sendMessage(b64toBlob(b64), { content: 'Resized Image'})
}


function sendMessage(img, obj=false) {
  console.log("send")
  const WH_URL = "YOUR_WEBHOOK_URL"
  if(!obj)
    obj = {
      content: ''
    }
  var myHeaders = new Headers();
  var formdata = new FormData();
  formdata.append("file", img, "img.png");
  formdata.append("payload_json", JSON.stringify(obj));
  var requestOptions = {
    method: 'POST',
    headers: myHeaders,
    body: formdata,
    redirect: 'follow'
  };
fetch(WH_URL, requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
}

const b64toBlob = (b64Data, contentType='', sliceSize=512) => {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];
  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}
<img id="img"/>
<p>
  <input type="file" onchange="readImg(this)"/>
</p>