【问题标题】:JavaScript fetch() Discord file upload results in a HTTP 400 "cannot send empty message, 50006" errorJavaScript fetch() Discord 文件上传导致 HTTP 400“无法发送空消息,50006”错误
【发布时间】:2020-11-05 17:15:09
【问题描述】:

我正在尝试使用 webhook 使用 vanilla javascript 将单个 PNG 文件上传到 Discord 频道,因为我无法使用 Discord.js。

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

POST https://discordapp.com/api/webhooks/ 400

随后是 console.log() 输出:

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

最后我包含了我的代码和网络控制台脚本,但我有 1 个问题:

  • 我做错了什么?我看到文件的Content-Dispositionform-data 类型,这与我的问题有什么联系吗?

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

我没有尝试使用 JQuery,尽管我可以并且更喜欢纯 Javascript 解决方案。

以下是我编写的代码,用于发送单个 PNG 图像以及 payload_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);

【问题讨论】:

  • 有什么更新吗?我遇到了同样的问题
  • 抱歉,我没有得到答复,看来您必须找到一种方法将图像文件存储在其他地方,或者对 Discord.js 所做的逆向工程来完成工作......在我的情况是,我们现在只是放弃了与 Discord 的挂钩,而且,我们将来也许可以使用 Discord.js ......无论如何,欢迎使用 vanilla js 回答。

标签: javascript file upload discord fetch-api


【解决方案1】:

构建一个简单的 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;
}

然后,我使用 fetch 将图像作为 blob 发送到 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>

【讨论】:

  • 我认为您需要详细说明遇到的问题。我运行你的代码 sn-p 并且它可以工作。
  • 我的英语不太流利,如果解释得不好真的很抱歉
  • 必须道歉的是我,我把你的答案误认为是一个问题(这当然是缺少的——它是一个答案:))。我是新来审查..
  • 呵呵呵呵,我是新来的回复
猜你喜欢
  • 2021-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-26
  • 2019-03-05
  • 2017-07-23
  • 2021-05-29
  • 2021-03-02
相关资源
最近更新 更多