0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Discordにwebhook経由でPOST(cURL)

Posted at

はじめに

Under MIT license1

Copyright (c) 2025 n138-kz

「MITライセンスの全文」の意訳

下記の条件を受け入れていただけるのであれば、誰でも自由に無料で、このソフトウェアをつかっていただくことができます。
このソフトウェアをコピーしてつかったり、配布したり、変更を加えたり、変更を加えたものを配布したり、商用利用したり、有料で販売したり、なんにでも自由につかってください。
このソフトウェアの著作権表示(「Copyright (c) 年 作者名」)と、このライセンスの全文(英語の文章)を、ソースコードのなかや、ソースコードに同梱したライセンス表示用の別ファイルなどに掲載してください。
このソフトウェアにはなんの保証もついていません。たとえ、このソフトウェアを利用したことでなにか問題が起こったとしても、作者はなんの責任も負いません。

書いてある内容はあっているとは限りません。
あくまで参考程度に、ほとんどが自分用のメモです。

検証にあたりdocker(almainux:8)環境で実行していますが、
通常のホスト環境においても動作するはずです。

文中に記載の変数っぽい表記は下記のとおりです。

変数名 主な用途
${discord_webhook_url} <URL>
Webhook Endpoint URL
${discord_avatar_url} <URL>
Webhook Icon
https://hangstuck.com/wp-content/uploads/2020/08/bash-official-icon-512x512-1.pngを設定している
${discord_username} <Text>
${HOSTNAME%%.*}を設定している
${HOSTNAME}=ホスト名(FQDN2
変数設定
discord_webhook_url=''
discord_avatar_url='https://hangstuck.com/wp-content/uploads/2020/08/bash-official-icon-512x512-1.png'
discord_username=${HOSTNAME%%.*}

環境

cURL: 7.61.1
# curl --version
curl 7.61.1 (x86_64-redhat-linux-gnu) libcurl/7.61.1 OpenSSL/1.1.1k zlib/1.2.11 nghttp2/1.33.0
Release-Date: 2018-09-05
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy

curl default

-X GET
-H 'Content-Type:application/x-www-form-urlencoded'

Discordにコマンドで写真(イラストなど)と文字を投げつける準備

やることリスト

Discordのアカウント作成

image.png3

自分でサーバ建てるか、どこかのサーバに招待してもらう

他人管理のサーバに招待してもらう場合は、Webhookの権限を付与してもらってください。
チャンネルごとにwebhook権限付与する/しないが設定できます。(詳細はググってみてください)
私はロールごとに設定してますが。

他人管理のサーバで本記事の内容を実施しKICKやBANされても知りません。自己判断で実施してください。
短時間連続POSTは嫌がる人がほとんどです。

image.png

image.png

Webhook URL作成
  1. チャンネルの編集 > 連携サービス > ウェブフック

  2. ウェブフックURLをコピーをポチってURLもらう
    image.png

Discordにコマンドで投げつける

これが基本形

POST https://discordapp.com/api/webhooks/channel_id/webhook_id
curl -X POST ${discord_webhook_url}
実際には content がないと弾かれる

JSON Error Codes4

curl -X POST ${discord_webhook_url}
{
  "message": "Cannot send an empty message",
  "code": 50006
}

content をつけてもう一度

curl -X POST -d 'content=Hello world.' ${discord_webhook_url}

image.png

コマンドが長くなったら \で行末の改行をエスケープ

行末の\あとには半角スペース含む文字を一切書かないこと
\でエスケープしてるのは EOL文字(CR, LF, CRLF)

curl \
  -X POST \
  -d 'content=Hello world.' \
  ${discord_webhook_url}

''""みたいな引用符の中以外であれば自由に改行できる
どうしても引用符の中で改行させたい場合は、一旦引用符を閉じる(引用符~引用符の間はスペースは入れない)

curl \
  -X POST \
  -d 'content=Hello world.'\
'Webhook test' \
  ${discord_webhook_url}

image.png

HTTP/GETすることも可能
curl -X GET ${discord_webhook_url}
{
  "application_id": null,
  "avatar": null,
  "channel_id": "(hide)",
  "guild_id": "(hide)",
  "id": "(hide)",
  "name": "Spidey Bot",
  "type": 1,
  "token": "(hide)",
  "url": "(hide)"
}

image.png 5

-X GETデフォルトなのでつけなくてもOK

HTTP/DELETEすることも可能

Webhook URL自体が失効します

curl -X DELETE ${discord_webhook_url}
curl -X GET ${discord_webhook_url}
curl -X GET ${discord_webhook_url}
{
  "message": "Unknown Webhook",
  "code": 10015
}

POSTしたときに出力を受け取りたいとき

POSTしたチャンネルの情報、投稿メッセージIDを見る方法がある。
何度もAPIを叩く(curlコマンドを実行する)必要はなく、URLの末尾に?wait=true6もしくは&wait=true6を追記するだけ。
デフォルト(?wait=true6が無い状態)では、Response-codeが204 NoContent7となり何も出力されないが、?wait=true6を追記することで200 OK8になり、色々見れる。

image.png

サンプル
curl \
  -X POST \
  -d 'content=Hello world.'\
  ${discord_webhook_url}?wait=true
{
  "type": 0,
  "content": "Hello world.",
  "mentions": [],
  "mention_roles": [],
  "attachments": [],
  "embeds": [],
  "timestamp": "2025-02-15T11:56:03.243000+00:00",
  "edited_timestamp": null,
  "flags": 0,
  "components": [],
  "id": "(hide)",
  "channel_id": "(hide)",
  "author": {
    "id": "(hide)",
    "username": "Spidey Bot",
    "avatar": null,
    "discriminator": "0000",
    "public_flags": 0,
    "flags": 0,
    "bot": true,
    "global_name": null,
    "clan": null,
    "primary_guild": null
  },
  "pinned": false,
  "mention_everyone": false,
  "tts": false,
  "webhook_id": "(hide)"
}

Content Type

把握してる限り、応答(response)はすべてapplication/json
要求(request)はapplication/json, application/x-www-form-urlencoded, multipart/form-dataのどれか

image.png9

curlコマンド実行時、リクエスト本体(Request-body(i.e. content=, etc))を
-dで指定する場合はapplication/x-www-form-urlencodedが、
-Fで指定する場合はmultipart/form-dataが自動で設定されるらしい。10

Rate Limits

連投する場合は Rate Limits11 に注意する。
image.png
RFC 658512準拠らしいがよくわからん。(429 Too Many Requests13返すってことくらいしかわからん)
連投しなければ問題なさそう。

だがとりあえず 5/pps っぽい
application/x-www-form-urlencoded
# date +%s;curl -I -X POST ${discord_webhook_url}
1739613504
HTTP/2 400
date: Sat, 15 Feb 2025 09:58:25 GMT
content-type: application/json
content-length: 58
x-ratelimit-bucket: 3d2712a9e4fe17cc9d3fed4a8e672e5f
x-ratelimit-limit: 5
x-ratelimit-remaining: 4
x-ratelimit-reset: 1739613506
x-ratelimit-reset-after: 1

1739613504= コマンド実行した時間
Sat, 15 Feb 2025 09:58:25 GMT(1739613505)= 要求受け取った時間(date)
1739613506= リセットされる時間(x-ratelimit-reset)

ウェブフックのアイコンカスタマイズ

content=と一緒にavatar_url=をつけてあげる

application/x-www-form-urlencoded
curl \
  -X POST \
  -d 'content=Hello world.' \
  -d 'avatar_url='${discord_avatar_url} \
  ${discord_webhook_url}

image.png

シェル(shell)が引用符内変数展開に対応しているなら

""で囲ってあげればスマート

curl \
  -X POST \
  -d 'content=Hello world.' \
  -d "avatar_url=${discord_avatar_url}" \
  ${discord_webhook_url}

アイコンと一緒に名前も

username=もつけてあげる

application/x-www-form-urlencoded
curl \
  -X POST \
  -d 'content=Hello world.' \
  -d 'avatar_url='${discord_avatar_url} \
  -d 'username='${discord_username} \
  ${discord_webhook_url}

image.png

ファイルといっしょに投げつける

ファイルを投げるときは Content-Typemultipart/form-data に設定する。

-H 'Content-Type:multipart/form-data'

サーバーのブースト具合にも寄るが、投げれるファイルはデフォルト(ギルドレベル:0)だと 10MiB(課金すれば最大100MBまでいける)
image.png
image.png

投げるだけならそんなに考慮はいらない。(日本語のようなマルチバイト文字でも行ける)
file=のあとに続けて@ファイル名 とすることでファイルを送信できる。
アップロードアイコン.{png|jpg|svg}

curl -X POST \
    -H 'Content-Type:multipart/form-data' \
    -F 'avatar_url='${discord_avatar_url} \
    -F 'username='${discord_username} \
    -F 'file=@アップロードアイコン.png' \
    -F 'content=Attached files.' \
    ${discord_webhook_url} | jq
image/pngだと画像として表示される

image.png

image/svgだとテキスト形式で表示される

image.png

複数のファイルを投げる

Rate Limits の件もあり、複数のファイルを短い間隔でポンポン投げるのは、あまり望ましく無い。

file= ではなく files= にすることで複数のファイルを同時に投げれる様になる。14

image.png 14

curl -X POST \
    -H 'Content-Type:multipart/form-data' \
    -F 'avatar_url='${discord_avatar_url} \
    -F 'username='${discord_username} \
    -F 'files[0]=@アップロードアイコン.png' \
    -F 'files[1]=@アップロードアイコン.svg' \
    -F 'content=Attached files.' \
    ${discord_webhook_url} | jq

image.png

html formの場合は<input type="file" name="files[]" />にして、受取り側(i.e. PHP)では$_FILES['files'][]になる感覚かな...

sample.html
<input type="file" name="files[]" />
sample.php
<?php
foreach($_FILES['files'][] as $key => $value){
    if(! is_uploaded_file($value['tmp_name'])){continue;}
    if(! move_uploaded_file($value['tmp_name'],$value['name'])){continue;}
    echo 'Upload succeed.'.PHP_EOL;
}

最大42個14のファイルまで受信してくれるっぽいので、そんなに不便はなさそう。

まとめて何個か投げる

2個とかならまだいい。
4個とかfiles[]=書くの大変だし、辛い( ๑╹⌓╹ )
なら、forでまわすべ

webpファイルをまとめて投げる場合はこれでいける(いけた)
他のやつ送る場合は1行目だけいじればいいはず。

Send the *.webp
curl_files=''; i=0; for f in *.webp; do curl_files=${curl_files}" -F file[$i]=@${f}";i=$((i+1));done; \
curl -X POST \
    -H 'Content-Type:multipart/form-data' \
    -F 'avatar_url='${discord_avatar_url} \
    -F 'username='${discord_username} \
    ${curl_files} \
    -F 'content=Attached files.' \
    ${discord_webhook_url} | jq

ファイルの一覧はcurlのオプション-Fといっしょに変数$curl_filesにぶち込んで、curl実行するときにべってやってる。

ワンライナーでできたらって思ったけど、これ以上いい方法あるのカナ...
image.png

12個くらいのファイルをまとめて投げようとしたら弾かれたんだけど、42個じゃないのか・・・

{
  "message": "Maximum number of allowed attachments in a message reached (10).",
  "code": 30015
}

見た目を上げるならembedsにもゴニョゴニョ

配列を標準で実装していて、かつコードもそんなに負担にならないphpとかpythonとかならいいんだろうけども、
shell scriptって 連想配列(associative array)や 配列(indexed array)みたいなやつに対してそんなに充実した機能があるわけじゃない(実際には?)ので、結構大変。

Array.push() とかあれば JSONの [] を自由に増やせるんだけど。

一応やったのが、↓なんですが
payload_json つくるのがメンド

details
request_json=''
request_json=${request_json}''
request_json=${request_json}'{'
request_json=${request_json}'"avatar_url":"'${discord_avatar_url}'",'
request_json=${request_json}'"username":"'${discord_username}'",'
request_json=${request_json}'"content":"Attached files.",'
request_json=${request_json}'"embeds":['
for f in *.webp; do
request_json=${request_json}'{'
request_json=${request_json}'"title":"Attached files.",'
request_json=${request_json}'"color":'$((16#ffa500))','
request_json=${request_json}'"image":{'
request_json=${request_json}'"url":"attachment://'${f}'"'
request_json=${request_json}'}'
request_json=${request_json}'},'
done
request_json=${request_json}']'
request_json=${request_json}'}'
request_json=$(echo ${request_json} | sed -e 's/\}},\]}$/}}]}/g')
echo ${request_json} | jq > payload_json.json

curl_files=''; i=0; for f in *.webp; do curl_files=${curl_files}" -F file[$i]=@${f}";i=$((i+1));done; \
curl_exec=$(curl -X POST \
    -H 'Content-Type:multipart/form-data' \
    -H 'Content-Disposition:form-data;name="payload_json"' \
    -F "payload_json=${request_json}" \
    ${curl_files} \
    ${discord_webhook_url}); echo ${curl_exec} | jq
    
curl_exec_id=$(echo ${curl_exec} | jq -r .id); \
echo ${curl_exec} | jq | tee ${curl_exec_id}.json;

image.png

15

enbedsの中のfieldsにはinline=true|falseの項目があって図のようにすることもできるみたい。(画像を左右に埋め込めないのかな)

details
request_json=''
request_json=${request_json}''
request_json=${request_json}'{'
request_json=${request_json}'"avatar_url":"'${discord_avatar_url}'",'
request_json=${request_json}'"username":"'${discord_username}'",'
request_json=${request_json}'"content":"Attached files.",'
request_json=${request_json}'"embeds":['
for f in *.webp; do
request_json=${request_json}'{'
request_json=${request_json}'"fields":[{"inline":true,"name":"fname","value":"'${f}'"},{"inline":true,"name":"username","value":"'${discord_username}'"}],'
request_json=${request_json}'"title":"'${f}'",'
request_json=${request_json}'"color":'$((16#ffa500))','
request_json=${request_json}'"image":{'
request_json=${request_json}'"url":"attachment://'${f}'"'
request_json=${request_json}'}'
request_json=${request_json}'},'
done
request_json=${request_json}']'
request_json=${request_json}'}'
request_json=$(echo ${request_json} | sed -e 's/\}},\]}$/}}]}/g')
echo ${request_json} | jq > payload_json.json

curl_files=''; i=0; for f in *.webp; do curl_files=${curl_files}" -F files[$i]=@${f}";i=$((i+1));done; \
curl_exec=$(curl -X POST \
    -H 'Content-Type:multipart/form-data' \
    -H 'Content-Disposition:form-data;name="payload_json"' \
    -F "payload_json=${request_json}" \
    ${curl_files} \
    ${discord_webhook_url}); echo ${curl_exec} | jq
    
curl_exec_id=$(echo ${curl_exec} | jq -r .id); \
echo ${curl_exec} | jq | tee ${curl_exec_id}.json;

image.png

image.png 14

payload_jsonって名前のヤツに入れれば良さそうってことで入れてみた。

公式マニュアルには最低限 content, embeds, files[n] 入れてね的なことが書いてあったので、とりあえずそれだけ。

This example demonstrates usage of the endpoint with payload_json and all content fields (content, embeds, files[n]) set.

サンプルだと message_reference, attachments があったけどなくても動くんだよなぁ

公式サンプル
--boundary
Content-Disposition: form-data; name="payload_json"
Content-Type: application/json

{
  "content": "Hello, World!",
  "embeds": [{
    "title": "Hello, Embed!",
    "description": "This is an embedded message.",
    "thumbnail": {
      "url": "attachment://myfilename.png"
    },
    "image": {
      "url": "attachment://mygif.gif"
    }
  }],
  "message_reference": {
    "message_id": "233648473390448641"
  },
  "attachments": [{
      "id": 0,
      "description": "Image of a cute little cat",
      "filename": "myfilename.png"
  }, {
      "id": 1,
      "description": "Rickroll gif",
      "filename": "mygif.gif"
  }]
}
--boundary
Content-Disposition: form-data; name="files[0]"; filename="myfilename.png"
Content-Type: image/png

[image bytes]
--boundary
Content-Disposition: form-data; name="files[1]"; filename="mygif.gif"
Content-Type: image/gif

[image bytes]
--boundary--

Discordにshell scriptで投げるなら複雑にしないほうが良い

やってみてわかった。
content=username=, avatar_url=くらいならまだいい。

embeds=だけはまじでおすすめしない。
中身が複雑すぎてタイパ悪すぎる...( ๑╹⌓╹ )

embedsは更にfieldsって項目があったりして自由にいじれるけど、それやるくらいだったらPythonとかで組んだほうが楽っすね。
CとかC++はできないけど、Pythonなら行け無いことも無いので、必要なときが来たら作ろう(きっとこない)

そういえば、Github Actionの無料枠が上限迎えてしまった。。。

あと13日つら・・・

  1. https://opensource.org/license/mit

  2. FQDN: Fully Qualified Domain Name(完全修飾ドメイン名)

  3. https://discord.com/register

  4. https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes

  5. https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types

  6. https://discord.com/developers/docs/resources/webhook#execute-webhook 2 3 4

  7. https://developer.mozilla.org/ja/docs/Web/HTTP/Status/204

  8. https://developer.mozilla.org/ja/docs/Web/HTTP/Status/200

  9. https://discord.com/developers/docs/reference#content-type

  10. https://qiita.com/att55/items/04e8080d1c441837ad42

  11. https://discord.com/developers/docs/reference#content-type

  12. https://datatracker.ietf.org/doc/html/rfc6585#section-4

  13. https://developer.mozilla.org/ja/docs/Web/HTTP/Status/429

  14. https://discord.com/developers/docs/reference#uploading-files 2 3 4

  15. color=は10進数の値を入力する。CSSで使用されるような形式からshellで変換するには $((16#ffa500)) #ffa500

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?