初心者に捧ぐDiscordのEmbed入門

## はじめに
この記事は[DiscordBotアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/discordbot)の6日目の記事です。
今回は、DiscordBotのメッセージ出力方法の一つ、Embed(埋め込み)メッセージについて書こうと思います

## Embed事始め
御存知の通りEmbedはDiscord上にリンク・文字・画像といった複合的要素をレイアウトして送信できるメッセージ形態です。
しかし皆さんはEmbedをどれくらい使っているでしょうか。
大抵のBot作成初心者さんは初めてのBotの出力を以下のようにプレーンテキストメッセージで実装しているのではないでしょうか[^1]。

```py
await message.channel.send("Hello!")
```

Embedは項目が多く、導入が難しいという初心者さんの声も聞かれますが、食わず嫌いは非常にもったいないです！
Embedメッセージを一度でも使うとその魅力に縛られ手放せなくなります。
今回は、Bot作成を初めて日が浅く、上記コードの出力を主としているような人に、Embedを布教するための記事になりそうです。

#### Embedを使用できるのはBotのみ
非常に優秀な機能なのでぜひ我々人間も使いたいところですが、現状人間ユーザーアカウントはEmbedを作成できません。
自由にEmbedを操る力はBotの特権です。[^2]


#### Botの出力はEmbedを使いたい
私はBotに出力させるメッセージは、特に理由がない限りEmbedを使用することをオススメしたいです。
Botは普通にメッセージを送信できるのになぜEmbedなのか、それは
**通常メッセージに比べて 圧倒的に視認性が高い**事が挙げられます。
次項で触れていきますが、EmbedはBotの出力に適した特徴がたくさんあるのです。
せっかくBotに与えられた特権機能、フルに活用して使いやすいBotを作ってみませんか？

## Embedのメリット
### 複数種類の情報を出力できる
Embedには、名前と値のセットである`field`を最大25セットまで追加したり、画像を`image`と`thumbnail`の大小2種類埋め込むことも可能です。

### Botの情報をコンパクトに格納できる
Embedのauthorエリアにはリンクも含めたbotや開発者の情報を記載できます。連絡先をリンクしておくとフィードバックが手軽かつスムーズになるかもしれません。

### フレーム色設定(後述)でメッセージの種類を視覚的に分類できる
Embedの特徴の一つが、左端の細いバーでしょう。実はこのバー、色を自由に変更できます。
これを利用して、正常な実行結果の緑、情報出力の青、エラーメッセージの赤、のようにバーの色の切り替えでメッセージの種類をよりわかりやすいものにできるでしょう。

(画像: エラーメッセージを赤色フレームで表示する例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398976/57100784-ba58-32e9-c436-a69dcd401ae0.png)

## Embedの構成要素

Embedには具体的にどんなパラメータがあるのか、主な要素を表にしてみました。

| 値 | 説明 | 上限 |
|:-:|:-:|:-:|
| title | Embedのタイトル |256文字
| description | Embedの説明。小さめの文字でレイアウトされる |2048文字
| color | Embedフレーム左のバーの色 |
| author | Embed作成者の情報エリア |256文字
| thumbnail | 小さい画像エリア |
| field | nameとvalueのセットからなるEmbedのメインコンテンツ |field数 : 25セット<br>name :256文字<br>value : 1024文字
| image | 大きな画像エリア |
| footer | フッター。開発者情報やライセンス等を記載できる |2048文字

## discord.pyでEmbedサンプルコード

```embed.py
import discord

client = discord.Client()
@client.event
# 今回はon_readyでログイン時に指定チャンネルにEmbedを送信させていますが、on_messageでユーザー入力に反応するときも要領は同じです。
async def on_ready(): 
    embed = discord.Embed( # Embedを定義する
                          title="Example Embed",# タイトル
                          color=0x00ff00, # フレーム色指定(今回は緑)
                          description="Example Embed for Advent Calendar", # Embedの説明文 必要に応じて
                          url="https://example.com" # これを設定すると、タイトルが指定URLへのリンクになる
                          )
    embed.set_author(name=client.user, # Botのユーザー名
                     url="https://repo.exapmle.com/bot", # titleのurlのようにnameをリンクにできる。botのWebサイトとかGithubとか
                     icon_url=client.user.avatar_url # Botのアイコンを設定してみる
                     )

    embed.set_thumbnail(url="https://image.example.com/thumbnail.png") # サムネイルとして小さい画像を設定できる

    embed.set_image(url="https://image.example.com/main.png") # 大きな画像タイルを設定できる

    embed.add_field(name="フィールド１",value="値１") # フィールドを追加。
    embed.add_field(name="フィールド２",value="値２")

    embed.set_footer(text="made by nashiroaoi", # フッターには開発者の情報でも入れてみる
                     icon_url="https://dev.exapmple.com/profile.png")

    channel = client.get_channel(CHANNEL_ID)

    await channel.send(embed=embed) # embedの送信には、embed={定義したembed名}

client.run(TOKEN)

```
実際に上記のEmbedを出力させてみます。
(画像URLはサンプルコードではプレースホルダになっているので具体的な値を入れています)
![embed](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398976/632b417b-c98c-27cc-06b2-cebab617d6a3.png "このはちゃんはいいぞ")

テスト用にお借りした画像URL
thumbnail : [Wikipedia pythonロゴ](https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:Python_logo_and_wordmark.svg)
image : [美雲このは壁紙](https://conoha.mikumo.com/wallpaper/)[^3]


## Embedのfield詳解
Embedのメインコンテンツの一つであるfield要素。
discord.pyでもfield要素の構築に便利な色々な操作が可能です。
いくつか見ていきましょう。

### add_field() -fieldの追加-
fieldをEmbedに追加するには、`add_embed()`メソッドを使用します。このメソッドには`name`、`value`、`inline`の3つのキーワード引数を取ります。`inline`は`bool`型で、これが`False`になると、1行に1fieldが配置されます。デフォルトは`True`です。

|inline=True|inline=False|
|:-:|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398976/86ac414a-2176-ff9b-a27a-0a0e4a997d4b.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398976/2d40e117-4c3c-f3c5-5783-ba6fda7308bb.png)|



### insert_field_at() -fieldの挿入-
`add_field()`で作成された新しいfieldは常にEmbedの最後尾に追加されていきます。すでに作成したfieldの間にあとからfieldを差し込みたいときは、`insert_field_at()`メソッドを使います。
`add_field()`メソッドと同じ3種類の引数に加え、挿入地点を指定する`index`引数を取ります。
`index`の値は**0スタート**であることに注意してください。
つまり、先頭フィールド(1番目)は0、2番目フィールドは1です。

```py
embed.add_field(name="name1", value="value1")  # フィールド1を追加
embed.add_field(name="name2", value="value2")  # フィールド2を追加
embed.insert_field_at(1, name="name3", value="value3")  # フィールド3を先頭から 2 番めに挿入
```
結果、insertしたfield3はfield2より後に書かれたにもかかわらず、先頭から2番めに入り込んでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398976/256c349d-81b9-81aa-0e53-e363984d5fbd.png)


### set_field_at() -fieldの編集-
このメソッドは、追加したfieldを新しいデータで上書きします。`add_field()`の引数3つに加え、上書きしたいfieldを指定する`index`引数を取ります。
### remove_field() -fieldの削除-
既に作成したfieldを削除するメソッドです。削除したいfieldを指定する`index`引数を取ります。
### clear_fields() -fieldの全削除-
Embed内の全fieldを削除します。引数は取りません。
## Embedハマりどころ
便利なEmbedですが、使用する上で注意が必要なポイントもいくつかあります。
私が過去実際ハマったものを中心に、Embed周りのトラブルを見ていきましょう。
#### Embedの表示がスマホで崩れる
スマホ(android)アプリでEmbed出力を表示したとき、Embedの表示が崩れ、極端な縦長になったりすることがあります。
私の調査の限りでは残念ながら対処法を発見することができませんでした。
#### Bad Requestエラーが出て送信できない
考えられる原因はいくつかあります。
##### 文字数制限
Embedには極端に大量の送信がされることを防ぐため、テキストメッセージの制限(2000字)のようにEmbedにもいくつかの制限があります。
[Embedの構成要素](#Embedの構成要素)の文字数制限のほか、**Embed内の文字総数が6000字以上**になるとBad Requestが発生し、送信できません。
##### field情報の欠落
`add_field()`メソッドには、`name`、`value`の2種類の引数が必須となります(inlineはデフォルト値`True`が適用されます)。
箇条書きを表現したくて、`name`だけ、`value`だけ、のfieldを追加しようとすると、Bad Requestが発生します。
箇条書きを表現したいときでも、両方に何かしら値を入れてやらなければいけません。
私がよくやる手として、`name`に絵文字や中黒などを入れる方法があります。
#### ローカルの画像を埋め込みたい
Embedの画像thumbnailやimageは、画像をURLで指定するためローカルの画像を埋め込むにはひと手間必要になります。
実際の流れを見てみましょう。

```py
import discord

client = discord.Client()

@client.event
async def on_ready():
    embed = discord.Embed(title="Local Image") # まずは普通にEmbedを定義
    fname="upload.png " # アップロードするときのファイル名 自由に決めて良いですが、拡張子を忘れないように
    file = discord.File(fp="ローカル画像パス",filename=fname,spoiler=False) # ローカル画像からFileオブジェクトを作成
    embed.set_image(url=f"attachment://{fname}") # embedに画像を埋め込むときのURLはattachment://ファイル名
    channel = client.get_channel(CHANNEL_ID)
    await channel.send(file=file, embed=embed) # ファイルとembedを両方添えて送信する

client.run(TOKEN)

```
より詳しい情報は[公式リファレンス](https://discordpy.readthedocs.io/ja/latest/faq.html#how-do-i-upload-an-image)を参照ください。

## あとがき
起承転結のないぶった切りな終わり方ですが、そろそろ筆を置こうとおもいます。
Embedを使うのは決して難しくはありません。もしそう思えるとしたらそれはEmbedの構成を把握しきれないことで圧倒されているのだと思います。
多くの項目を設定できるEmbedですが、各要素をすべて使う必要はありません。中には必要性の薄い要素や、出力内容次第ではむしろゴチャゴチャさせないために要素を省くべきケースもあります。
最初からフルに使いこなそうとはせず、出力したい内容を最小限埋め込んだシンプルなものから始めていくと良いと思います。
この記事が脱プレーンテキストメッセージのきっかけになれば執筆者冥利に尽きることです。

それでは少し早いですが、Merry Christmas!良いお年を。

[^1]: 断じてプレーンテキストメッセージが悪いわけではありません。が、今回はEmbed布教記事ということで少し下げさせてください。
[^2]: 人間ユーザーがリンクを送信したとき自動でリンク先のタイトルやサムネイルがEmbedとして生成されますが、あれは自由に操作できるとは到底言えないのでノーカンとします。
[^3]: conoHaの案件とかではありません。私の趣味です。