Flex Message とは
まずは公式ドキュメントのリンクからどうぞ。
Flex Messageは、通常のLINEメッセージに比べ、より豊かでインタラクティブなレイアウトが可能なメッセージです。通常のLINEメッセージでは、テキスト、画像、動画など1種類のソースしか配信されません。しかし、Flex Messageでは、CSS Flexible Box(CSS Flexbox) (opens new window)の仕様に基づいて、レイアウトを自由にカスタマイズできます。
Flex Messageの構成要素は、コンテナ、ブロック、コンポーネントです。各Flex Messageは、メッセージバブルを含むコンテナという単一の最上位構造を持ちます。コンテナには複数のメッセージバブルを含めることができます。バブルはブロックで構成され、ブロックはコンポーネントで構成されています。
これだけでは何を言っているか全然わかりません。公式のサンプル動画を見てみましょう。
このように、レイアウトを自分でカスタマイズできるのが Flex Message です。Flex Message は、Bubble という角丸長方形が一つの単位になります。この Bubble はクイックリプライ同様に横に複数並べることができ、FlexCarousel と呼ばれます(画像右下のサンプル)。
Flex Message のレイアウトは、原則 JSON によって書きます。Flex Message Simulator という公式のGUIで出来上がりがどうなるかの確認が可能ですが、このサンプルの JSON がどうなっているか、</> View as JSON のボタンを押して確認してみて下さい。かなり長い量の文字列が出てくると思います。
JSON の手書きは本当に骨が折れます。何か要素を追加しようと思った時に、ネストのどの深さまで行けばいいのか、親要素は何なのか、パッと見ではわかりにくことが多いからです。
ですが、line-bot-sdk
の v3 には、Flex Message を構成する各要素(上記の説明ではコンポーネント)がクラスとして用意されています。これらをオブジェクトとして積み重ねていくことで、JSON を一切書くことなしに Flex Message が作成可能です。というか、この LINE Messaging API 自体も本来は JSON でやり取りしているんですが、SDK を使ってプログラムを書いているおかげで今まで JSON に触れずに済んでいると言えますね。
Flex Message の構成要素
これに関しては他に良記事がたくさんあるかと思いますが、JSON そのものではなく line-bot-sdk
のクラスと関連して概説します。以下のクラスは、全て linebot.v3.messaging
内にあります。
-
FlexBubble
:- 最上位の要素、一つのメッセージ
- 複数を横に並べる場合は、
FlexCarousel
内に配置する -
header
,hero
(画像配置),body
,footer
といった領域がある - https://developers.line.biz/ja/reference/messaging-api/#bubble
-
FlexBox
:- 子要素を配置していくための箱。
- 子要素を水平方向
horizontal
・垂直方向vertical
のどちらに配置するかを指定できる - 子要素は
contents
プロパティに、リストとして格納する -
FlexBubble
のheader
やbody
などは、FlexBox
を必ず指定しなければならない - https://developers.line.biz/ja/reference/messaging-api/#box
-
FlexText
:-
FlexBox
内に配置するテキスト要素 -
text
プロパティに文字列を指定する - または、
contents
プロパティにFlexSpan
をリストとして格納する - https://developers.line.biz/ja/reference/messaging-api/#f-text
-
-
FlexSeparator
:-
FlexBox
内に配置する仕切り線要素 - https://developers.line.biz/ja/reference/messaging-api/#separator
-
-
FlexSpan
-
FlexText
内に配置する区切られたテキスト要素 - 各 span ごとに、異なるフォントサイズや色を指定できる
- HTML の <span> のようなもの
- https://developers.line.biz/ja/reference/messaging-api/#span
-
とりあえずこれだけ知っておけば、ある程度は作れると思います。VScode などの IDE で開発しているならプロパティ名がキーワード変数として表示されますし、APIドキュメントを読み込まずとも、細かい調整は実際にコーディングしながら学んでいけると思います。Flex Message Simulator でもプロパティ名は表示されますので、それを参考にするのも良いと思います。
とりあえず作る
既に歌詞を表示する機能を作りましたが、そのままメッセージとして送信するのでは文字も小さいし、見栄えも良くありません。ということで、Flex Message を使ってかっちょいいメッセージにしたいと思います。完成形はこれです。
曲名と、アルバム名、リリース年をヘッダに表示するために、こちらのレポジトリにある album_year.csv
も併用します。まずは、これを辞書(または pd.Series
)として読み込んでおきます。
さらに、前回コードの冗長化を減らすために調子に乗ってクラス化してしまったので、その修正も必要になってきます。曲名が一つに確定した時に TextMessage
オブジェクトとして返していたものを、型確認して文字列ならば TextMessage
として、そうでないならば FlexMessage
オブジェクトとして返すように修正します。FlexMessage
の場合は altText
というチャット一覧画面などで代わりに表示される代わりにテキストを指定してあげる必要があります(ここでは曲名にしています)。以下のコードの真ん中のあたりです。
## CLASS FOR GET INFORMATION BY SONG TITLE
class _GetBySongTitle:
def __init__(self, col_name, mode_prefix='', decorate=lambda x: x):
self.data = DATA[col_name] ## pd.Series : index - song title
self.prefix = mode_prefix
self.decorate = decorate ## function to add sth to reply message
def get(self, query:str) -> list:
songtitles = search_song_title(query)
if len(songtitles) == 0: ## no candidate
reply = 'NOT FOUND:\nPlease try again with different words.'
messages = [TextMessage(text=reply)]
elif len(songtitles) == 1: ## only one candidate -> return URL
songtitle = songtitles[0]
reterieved = self.data[songtitle] ## retrieve data
reply = self.decorate(reterieved, songtitle)
if type(reply) == str:
messages = [TextMessage(text=reply)]
else:
messages = [FlexMessage(altText=songtitle, contents=reply)]
else:
quickreply = QuickReply(items=[]) ## instantiation
for song in songtitles:
if len(song) > 20: ## max characters : 20
label = song[:19] + '…'
else:
label = song
item = QuickReplyItem(action=MessageAction(label=label, text=self.prefix+song))
quickreply.items.append(item)
messages = [TextMessage(text='candidate songs:', quickReply=quickreply)]
return messages
肝心の Flex Message 作成関数はこちらになります。
ALBUM_YEAR = pd.read_csv('data/album_year.csv', index_col='album')['year'] ## pd.Series
def create_flex_lyrics(lyrics, songtitle):
## GET SONG INFO
album = DATA.loc[songtitle, 'album']
year = ALBUM_YEAR[album]
## PREPARE BUBBLE CONTAINER
bubble_container = FlexBubble(size='giga')
## ADD HEADER WITH SONG TITLE, ALBUM, YEAR
song_title_text = FlexText(text=songtitle, color='#FFFFFF', size='xl', weight='bold')
song_album_text = FlexText(text=f'{album} ({year})', color='#FFFFFF66', size='lg')
header_box = FlexBox(
layout='vertical',
contents=[song_title_text, song_album_text],
spacing='sm',
backgroundColor='#0367D3',
paddingAll='xxl'
)
bubble_container.header = header_box
## ADD EMPTY BOX TO BODY
bubble_container.body = FlexBox(
layout='vertical',
spacing='xxl', ## space between each paragraph
contents=[]
)
## INSERT EACH LINE TO PARAGRAPH BOX
## paragraphs = list of list of lines e.g [[Jojo was..., But he...], [Get Back, ...]]
paragraphs = [paragraph.split('\n') for paragraph in lyrics.split('\n\n')]
for paragraph in paragraphs:
para_box = FlexBox(layout="vertical", contents=[], spacing='sm')
for line in paragraph:
para_box.contents.append(FlexText(text=line, size='lg', wrap=True))
bubble_container.body.contents.append(para_box)
return bubble_container
これだけでは何をやっているかサッパリだと思いますので、階層構造を書きますとこのようになっています。こういった階層構造関係は Web を作ったことがある方は DOM でお馴染みだと思います。
FlexBubble
├ header
| └ FlexBox
| ├ FlexText (曲名)
| └ FlexText (アルバム名、リリース年)
└ body
└ FlexBox
├ FlexBox (パラグラフ)
| ├ FlexText (一行)
| └ FlexText (一行)
├ FlexBox (パラグラフ)
| ├ FlexText (一行)
| └ FlexText (一行)
...
上述したように、FlexBubble
の header
および body
は入れ物である FlexBox
でなければなりません。その FlexBox
の contents
の中に、子要素をさらに入れていく形になります。歌詞はパラグラフごとに分かれているので、その一かたまりをさらに FlexBox
とし、その中に歌詞を一行ずつ入れていきます。なんでこんなことをしているかというと、折り返しの行間と改行に微妙な差が付くからです。折り返しは wrap=True
を設定することでできます。
各プロパティの解説
階層構造がわかったところで、細かいプロパティ設定の説明に入ります。
-
FlexBubble
- https://developers.line.biz/ja/reference/messaging-api/#bubble
-
size
: 大きさを指定。SI接頭語が利用されている。ここではgiga
という一番大きいものを使用。 -
header
: ヘッダー部分。FlexBox
を指定する -
body
: ボディ部分。FlexBox
を指定する
-
FlexBox
- https://developers.line.biz/ja/reference/messaging-api/#box
-
contents
: 必須要素。とりあえずインスタンス化して後から追加する際は、空リストにしておく -
layout
: 必須要素。vertical
、horizontal
、baseline
の3つから選ぶ -
spacing
: 子要素同士の間隔を決める。xs
,sm
,md
,lg
,xl
,xxl
から選ぶか、px で指定 -
backgroundColor
: 背景色。hexカラーで指定
-
FlexText
- https://developers.line.biz/ja/reference/messaging-api/#f-text
-
text
: 表示テキスト -
size
: フォントサイズ。xxs
,xs
,sm
,md
,lg
,xl
,xxl
,3xl
,4xl
,5xl
から選ぶか、px で指定 -
weight
: 太字はbold
を指定 -
color
: 文字色。hexカラーで指定 -
wrap
: テキストの折り返しをするかどうか
これ以外にも設定できる項目はたくさんあります。何をどういじるとどう変わるかは、Flex Message Simulator で遊んでみると一目瞭然です。
レイアウト確認の注意点
パラメータを変えるたびにいちいちボットで送信してレイアウトを確認するのも面倒ですから、基本的には GUI である Flex Message Simulator を使うことになります。そのためには JSON 形式で出力する必要があります。
Flex...
クラスには全て .to_json()
というメソッドがあり、これで JSON 形式にして出力できますので、それを Flex Message Simulator の View as JSON の部分にコピペして確認が可能です。以下は FlexBubble
オブジェクトがどう出力されるかを notebook 上で試した結果です。
この to_json()
の結果の、最初と最後のクォーテーションマーク '
を除いた部分をそのままコピペし、Flex Message Simulator に貼り付けてもいいのですが、上記の例のようにテキストに日本語が入っていると、以下のようになってしまいます。
これ、最初は「Flex Message Simulator が Unicode エスケープされた文字を扱えないという問題があるのでは?」と思っていたのですが、完全に私の間違いでした。\\u3042
のようにバックスラッシュ \
が2つ繋がってしまっているせいで、JSON が正しく認識できていないのです。LINEさん疑って本当にごめんなさい。
正しい方法は、print(flex.to_json())
のように JSON文字列をプリントして、バックスラッシュエスケープのための最初の \
を表示させないことです。プリントすれば文字列であることを示す最初と最後の '
も表示されませんし、コピーも楽です。JSONは必ず print せよ、というのが私にとっても教訓となりました。
ちなみに日本語だけでなく、don't
のようにシングルクォーテーションマークが入ってしまう場合でもエスケープのためのバックスラッシュが入ってしまうため、同様の問題が起きます。めんどくさがらずに、必ず print しましょう。
また、Flex Message Simulator に貼り付けるのはあくまで本体部分の JSON のみです。メッセージオブジェクトではない FlexBubble
の JSON 出力であればそのまま貼り付けていいですが、 FlexMessage
でメッセージオブジェクト化された物の JSON は注意が必要です。具体的には以下のようになっていますが、この場合は "contents" 以下の {"type": "bubble" ...}
だけコピーすれば良いです。
{
"type": "flex",
"altText": "alt",
"contents": {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "あいうえお"
}
]
}
}
}
逆に、Postman などで API を使って送信テストをする際には、FlexMessage
でメッセージオブジェクト化したものを JSON出力する必要があります。
次回予告
ポ
ストバック
Flex Message はさらにもっと複雑なこともできますが、SDK v3 仕様での Flex Message の触りとしては今回で終了し、次回はポストバックについて扱おうと思います。これを使うことで、ボットの可能性がさらに広がります。
目次 : [無料][2024年版] LINE Messaging API v3 + Python(Flask) でボットを作る
GitHub レポジトリ
older_version
内に、各回時点での app.py
と requirements.txt
があります。