はじめに
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のアカウント作成
自分でサーバ建てるか、どこかのサーバに招待してもらう
他人管理のサーバに招待してもらう場合は、Webhookの権限を付与してもらってください。
チャンネルごとにwebhook権限付与する/しないが設定できます。(詳細はググってみてください)
私はロールごとに設定してますが。
他人管理のサーバで本記事の内容を実施しKICKやBANされても知りません。自己判断で実施してください。
短時間連続POSTは嫌がる人がほとんどです。
Discordにコマンドで投げつける
これが基本形
POST https://discordapp.com/api/webhooks/channel_id/webhook_id
curl -X POST ${discord_webhook_url}
実際には content がないと弾かれる
{
"message": "Cannot send an empty message",
"code": 50006
}
content をつけてもう一度
curl -X POST -d 'content=Hello world.' ${discord_webhook_url}
コマンドが長くなったら \で行末の改行をエスケープ
行末の\
あとには半角スペース含む文字を一切書かないこと
\
でエスケープしてるのは 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}
HTTP/GETすることも可能
{
"application_id": null,
"avatar": null,
"channel_id": "(hide)",
"guild_id": "(hide)",
"id": "(hide)",
"name": "Spidey Bot",
"type": 1,
"token": "(hide)",
"url": "(hide)"
}
-X GET
はデフォルトなのでつけなくてもOK
HTTP/DELETEすることも可能
Webhook URL自体が失効します
curl -X DELETE ${discord_webhook_url}
curl -X GET ${discord_webhook_url}
{
"message": "Unknown Webhook",
"code": 10015
}
POSTしたときに出力を受け取りたいとき
POSTしたチャンネルの情報、投稿メッセージIDを見る方法がある。
何度もAPIを叩く(curlコマンドを実行する)必要はなく、URLの末尾に?wait=true
6もしくは&wait=true
6を追記するだけ。
デフォルト(?wait=true
6が無い状態)では、Response-codeが204 NoContent7となり何も出力されないが、?wait=true
6を追記することで200 OK8になり、色々見れる。
サンプル
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
のどれか
curlコマンド実行時、リクエスト本体(Request-body(i.e. content=
, etc))を
-d
で指定する場合はapplication/x-www-form-urlencoded
が、
-F
で指定する場合はmultipart/form-data
が自動で設定されるらしい。10
Rate Limits
連投する場合は Rate Limits11 に注意する。
RFC 658512準拠らしいがよくわからん。(429 Too Many Requests13返すってことくらいしかわからん)
連投しなければ問題なさそう。
だがとりあえず 5/pps っぽい
# 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=
をつけてあげる
curl \
-X POST \
-d 'content=Hello world.' \
-d 'avatar_url='${discord_avatar_url} \
${discord_webhook_url}
シェル(shell)が引用符内変数展開に対応しているなら
""
で囲ってあげればスマート
curl \
-X POST \
-d 'content=Hello world.' \
-d "avatar_url=${discord_avatar_url}" \
${discord_webhook_url}
アイコンと一緒に名前も
username=
もつけてあげる
curl \
-X POST \
-d 'content=Hello world.' \
-d 'avatar_url='${discord_avatar_url} \
-d 'username='${discord_username} \
${discord_webhook_url}
ファイルといっしょに投げつける
ファイルを投げるときは Content-Type
を multipart/form-data
に設定する。
-H 'Content-Type:multipart/form-data'
サーバーのブースト具合にも寄るが、投げれるファイルはデフォルト(ギルドレベル:0)だと 10MiB
(課金すれば最大100MBまでいける)
投げるだけならそんなに考慮はいらない。(日本語のようなマルチバイト文字でも行ける)
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
複数のファイルを投げる
Rate Limits の件もあり、複数のファイルを短い間隔でポンポン投げるのは、あまり望ましく無い。
file=
ではなく files=
にすることで複数のファイルを同時に投げれる様になる。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
html formの場合は<input type="file" name="files[]" />
にして、受取り側(i.e. PHP)では$_FILES['files'][]
になる感覚かな...
<input type="file" name="files[]" />
<?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行目だけいじればいいはず。
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実行するときにべってやってる。
ワンライナーでできたらって思ったけど、これ以上いい方法あるのカナ...
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;
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;
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日つら・・・
-
https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes ↩
-
https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types ↩
-
https://discord.com/developers/docs/resources/webhook#execute-webhook ↩ ↩2 ↩3 ↩4
-
https://discord.com/developers/docs/reference#content-type ↩
-
https://discord.com/developers/docs/reference#content-type ↩
-
https://discord.com/developers/docs/reference#uploading-files ↩ ↩2 ↩3 ↩4
-
color=
は10進数の値を入力する。CSSで使用されるような形式からshellで変換するには$((16#ffa500))
#ffa500
↩