3
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?

[無料][2024年版] LINE Messaging API v3 + Python(Flask) でボットを作る [その8 - Flex Message 編]

Last updated at Posted at 2024-01-07

Flex Message とは

まずは公式ドキュメントのリンクからどうぞ。

Flex Messageは、通常のLINEメッセージに比べ、より豊かでインタラクティブなレイアウトが可能なメッセージです。通常のLINEメッセージでは、テキスト、画像、動画など1種類のソースしか配信されません。しかし、Flex Messageでは、CSS Flexible Box(CSS Flexbox) (opens new window)の仕様に基づいて、レイアウトを自由にカスタマイズできます。

Flex Messageの構成要素は、コンテナ、ブロック、コンポーネントです。各Flex Messageは、メッセージバブルを含むコンテナという単一の最上位構造を持ちます。コンテナには複数のメッセージバブルを含めることができます。バブルはブロックで構成され、ブロックはコンポーネントで構成されています。

これだけでは何を言っているか全然わかりません。公式のサンプル動画を見てみましょう。

bubbleSamples-Update1.96cf1f73.png

このように、レイアウトを自分でカスタマイズできるのが 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 内にあります。

とりあえずこれだけ知っておけば、ある程度は作れると思います。VScode などの IDE で開発しているならプロパティ名がキーワード変数として表示されますし、APIドキュメントを読み込まずとも、細かい調整は実際にコーディングしながら学んでいけると思います。Flex Message Simulator でもプロパティ名は表示されますので、それを参考にするのも良いと思います。

とりあえず作る

既に歌詞を表示する機能を作りましたが、そのままメッセージとして送信するのでは文字も小さいし、見栄えも良くありません。ということで、Flex Message を使ってかっちょいいメッセージにしたいと思います。完成形はこれです。

IMG_6007.PNG

曲名と、アルバム名、リリース年をヘッダに表示するために、こちらのレポジトリにある album_year.csv も併用します。まずは、これを辞書(または pd.Series)として読み込んでおきます。

さらに、前回コードの冗長化を減らすために調子に乗ってクラス化してしまったので、その修正も必要になってきます。曲名が一つに確定した時に TextMessage オブジェクトとして返していたものを、型確認して文字列ならば TextMessage として、そうでないならば FlexMessage オブジェクトとして返すように修正します。FlexMessage の場合は altText というチャット一覧画面などで代わりに表示される代わりにテキストを指定してあげる必要があります(ここでは曲名にしています)。以下のコードの真ん中のあたりです。

botfuncs.py
## 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 作成関数はこちらになります。

app.py
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 (一行)
   ...

上述したように、FlexBubbleheader および body は入れ物である FlexBox でなければなりません。その FlexBoxcontents の中に、子要素をさらに入れていく形になります。歌詞はパラグラフごとに分かれているので、その一かたまりをさらに FlexBox とし、その中に歌詞を一行ずつ入れていきます。なんでこんなことをしているかというと、折り返しの行間と改行に微妙な差が付くからです。折り返しは wrap=True を設定することでできます。

各プロパティの解説

階層構造がわかったところで、細かいプロパティ設定の説明に入ります。

  • FlexBubble
  • FlexBox
    • https://developers.line.biz/ja/reference/messaging-api/#box
    • contents : 必須要素。とりあえずインスタンス化して後から追加する際は、空リストにしておく
    • layout : 必須要素。verticalhorizontalbaseline の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 上で試した結果です。

Screenshot 2024-01-08 at 04.57.49.png

この to_json() の結果の、最初と最後のクォーテーションマーク ' を除いた部分をそのままコピペし、Flex Message Simulator に貼り付けてもいいのですが、上記の例のようにテキストに日本語が入っていると、以下のようになってしまいます。

Screenshot 2024-01-08 at 05.02.33.png

これ、最初は「Flex Message Simulator が Unicode エスケープされた文字を扱えないという問題があるのでは?」と思っていたのですが、完全に私の間違いでした。\\u3042 のようにバックスラッシュ \ が2つ繋がってしまっているせいで、JSON が正しく認識できていないのです。LINEさん疑って本当にごめんなさい。

正しい方法は、print(flex.to_json()) のように JSON文字列をプリントして、バックスラッシュエスケープのための最初の \ を表示させないことです。プリントすれば文字列であることを示す最初と最後の ' も表示されませんし、コピーも楽です。JSONは必ず print せよ、というのが私にとっても教訓となりました。

Screenshot 2024-01-08 at 08.15.37.png

ちなみに日本語だけでなく、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.pyrequirements.txt があります。

3
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
3
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?