How To Create a LINE ChatBot

  1. Create an Account
  2. Create a Channel
  3. Webhook - A Simple "Echo"
  4. Reply an Image File (JPG, PNG or GIF)
  5. Reply a Picture + a Message
  6. Get the User ID
  7. Actively Push Messages to Users
  8. Broadcast a Text Message
  9. Broadcast a Flex Message
  10. Handle an Uploaded Image File
  11. Recognize an Uploaded Image File with PIL
  12. Handling Other Types of Files
  13. Flex Messages
  14. Flex Hierarchy
  15. Carousel
  16. Button
  17. Postback

Create an Account

  1. Go to LINE Business ID and click "LINE account". (I assume that each of you already has an LINE account.)
    [Login]

    If this is the first that you create a LINE ChatBot, you will be required to create a Business ID.
    [Create Business ID]

    You will then be required to create an "Official Account".
    [Create Official
Account]

    This step will authenticate you via SMS (Short Message Service, 簡訊).
    [SMS Authentication]

  2. Actually, you may create more than one "Official Account" - one account for each ChatBot channel.
    [Official Account]
  3. You obtain a "Bot Basic ID" (e.g., "@160qyrjz").
    [Account Created]
  4. Click "Verify Later" and go to "LINE Official Account Manager" (in my case, it will be "https://manager.line.biz/account/@160qyrjz/").
  5. You don't need to start gathering friends at this moment, so you may simply close the following pop-up window and go to the home screen of "LINE Official Account Manager".
    [Gain Friends]
  6. Now the official account is automatically added as your LINE friend. However, if you sent it any message, it would be unable to respond yet, because you haven't written programs to tell it how to respond.

Create a Channel

  1. Click "Settings" at the top-right corner, then click "Messaging API" at the left pane. Enable Messaging API.
  2. Select a Provider.
  3. If you haven't decided your privacy policy yet, simply press "OK" to skip it. You may edit it later by visiting LINE Developer Console and modify the "Basic Settings" of the channel.
    [Privacy Policy]
  4. It wants you to confirm that you want to be linked to the provider. Press OK.
    [Linked to Provider]
  5. Now you have successfully created a LINE channel. You don't need to write down the "Channel Secret". You can always find it in "LINE Developers Console" later.
    [Messaging API]

Webhook - A Simple "Echo"

  1. As shown in the figure of https://developers.line.biz/en/docs/messaging-api/getting-started/, you have to run a Flask program, which receives a message from the LINE Platform, and replies accordingly. For security reason, the message must be transmitted by HTTPS.
    channel_arch
  2. Use SSH to connect to "kghs2.ncnu.net". Login as "s114xxx".
  3. Go to LINE Developers Console. Select your channel.
  4. Create a Python program "chatbot.py":
    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, TextSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
    
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
    
        return 'OK'
    
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        msg = 'You said: ' + msg.upper()
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=msg))
    
  5. Run "flasks --app chatbot.py run". Note that this will run your Flask over HTTPS.
  6. Go to "Messaging API" and edit your Webhook URL to be "https://kghs2.ncnu.net:140xx/callback". (Don't forget the "/callback".) Click "Verify". You should see "Success - OK". [Success]
  7. Enable "Use webhook".
  8. Among the items below "Use webhook", there is an item "Auto-reply messages". Click its "Edit". Disable "Auto-response messages". (This is the one which replies "感謝您的訊息!很抱歉,本帳號無法個別回覆用戶的訊息。 敬請期待我們下次發送的內容喔.")
  9. Now go to your LINE App and send a message "Good afternoon" to this channel (NCNU_ACM). You will see the response.
  • If you want to change your account name and icon, go to LINE Official Account Manager and "Edit Profile".

    Reply an Image File (JPG, PNG or GIF)

    
    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, TextSendMessage, ImageSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
    
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
    
        return 'OK'
    
    PREVIEW_URL = 'https://ipv6.ncnu.org/Images/ncnu_logo-100x100.png'
    IMAGE_URL = 'https://ipv6.ncnu.org/Images/ncnu_logo-500x500.jpg'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        image_message = ImageSendMessage(
            original_content_url = IMAGE_URL,
            preview_image_url=PREVIEW_URL)
        line_bot_api.reply_message(
            event.reply_token,
            image_message) 

    Reply a Picture + a Message

    The reply_token is valid for replying only one message. If you want to reply a text message and a picture, you may reply a list of messages:
    line_bot_api.reply_message(
            event.reply_token,
            [TextSendMessage(text="NCNU"), image_message])

    Get the User ID

    
    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, TextSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
    
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
    
        return 'OK'
    
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        msg = 'Version 1.4\nYou said: ' + msg.upper()
        user_id = event.source.user_id
        profile = line_bot_api.get_profile(user_id)
        display_name = profile.display_name
        msg += f'\nuser_id: {user_id}\ndisplay_name: {display_name}'
        msg += f'\nlanguage={profile.language}\npicture_url={profile.picture_url}'
        msg += f'\nstatus_message={profile.status_message}\nuser_id={profile.user_id}'
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=msg)) 

    Actively Push Messages to Users

    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, TextSendMessage, ImageSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
    
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
    
        return 'OK'
    
    
    PREVIEW_URL = 'https://ipv6.ncnu.org/Images/ncnu_logo-100x100.png'
    IMAGE_URL = 'https://ipv6.ncnu.org/Images/ncnu_logo-500x500.jpg'
    USERS = []
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        if msg.lower().strip() == '/subscribe':
            USERS.append(event.source.user_id)
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='Successful Subscription'))
    
    @app.route('/push')
    def push():
        for user in USERS:
            notify(user)
        return f'Messages pushed to {len(USERS)} users.'
    
    def notify(user_id):
    image_message = ImageSendMessage(
            original_content_url = IMAGE_URL,
            preview_image_url=PREVIEW_URL)
        text_message = TextSendMessage(
            text='Do you want to order lunch? 學餐三樓特價中!')
        line_bot_api.push_message(
            to=user_id,
            messages=[text_message, image_message])

    Broadcast a Text Message to Users in a Channel

    def broadcast_text():
        # 1. Configuration
        # Replace 'YOUR_CHANNEL_ACCESS_TOKEN' with your actual token
        ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN')
        URL = 'https://api.line.me/v2/bot/message/broadcast'
    
        headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {ACCESS_TOKEN}'
        }
        # 2. Message Payload
        # You can send up to 5 messages in a single broadcast
        data = {
            "messages": [
                {
                    "type": "text",
                    "text": "Hello! This is a broadcast message from NCNU_CSIE_Solomon 🚀"
                }
            ]
        }
        # 3. Execution
        response = requests.post(URL, headers=headers, data=json.dumps(data))
        # 4. Result Handling
        if response.status_code == 200:
            print("Broadcast sent successfully!")
        else:
            print(f"Error: {response.status_code}")
            print(response.text)

    Broadcast a Flex Message

    def broadcast_flex():
        # 1. Configuration
        # Replace 'YOUR_CHANNEL_ACCESS_TOKEN' with your actual token
        ACCESS_TOKEN = os.getenv('CHANNEL_ACCESS_TOKEN')
        URL = 'https://api.line.me/v2/bot/message/broadcast'
        headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {ACCESS_TOKEN}'
        }
        # 2. Message Payload
        flex_json_string = r'''{
          "type": "bubble",
          "footer": {
            "type": "box",
            "layout": "vertical",
            "contents": [
              {
                "type": "button",
                "style": "primary",
                "action": {
                  "type": "uri",
                  "label": "CNN",
                  "uri": "https://cnn.com/"
                }
              },
              {
                "type": "button",
                "style": "secondary",
                "action": {
                  "type": "message",
                  "label": "OK",
                  "text": "ok"
                }
              }
            ]
          }
        }'''
        data = json.loads(flex_json_string)
        # 3. Execution
        response = requests.post(URL, headers=headers, data=json.dumps(data))
        # 4. Result Handling
        if response.status_code == 200:
            print("Broadcast sent successfully!")
        else:
            print(f"Error: {response.status_code}")
            print(response.text)
    
    

    Handle an Uploaded Image File

    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, ImageMessage,
        TextSendMessage, ImageSendMessage, 
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='Please upload a picture.'))
    
    STATIC_DIR = 'static/user_uploads'
    os.makedirs(STATIC_DIR, exist_ok=True)
    
    @handler.add(MessageEvent, message=ImageMessage)
    def handle_image_message(event):
        message_id = event.message.id
        # print(f'User uploads a file with id={message_id}')
        message_content = line_bot_api.get_message_content(message_id)
        file_path = os.path.join(STATIC_DIR, f'{message_id}.jpg')
        with open(file_path, 'wb') as f:
            # Iterate over the content in chunks and write to the file
            for chunk in message_content.iter_content():
                f.write(chunk)
        print(f"File successfully saved at: {file_path}")

    Recognize an Uploaded Image File with PIL

    from flask import Flask, request, abort
    from PIL import Image
    import io
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, ImageMessage,
        TextSendMessage, ImageSendMessage, 
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='Please upload a picture.'))
    
    @handler.add(MessageEvent, message=ImageMessage)
    def handle_image_message(event):
        message_id = event.message.id
        message_content = line_bot_api.get_message_content(message_id)
        image_bytes = io.BytesIO(message_content.content)
        with Image.open(image_bytes) as img:
            width, height = img.size  # img.size returns a tuple (width, height)
            format = img.format       # e.g., 'JPEG' or 'PNG'
        msg = f'Received file - Dimension: {width}x{height}, Format: {format}'
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=msg)
        )

    Handling Other Types of Files

    1. In addition to ImageMessage, the LINE platform also recognizes AudioMessage (.m4a, .mp3) and VideoMessage (.mp4) types. Please make sure to use correct file extensions.
    2. When a user uploads a file that is not a recognized media type (image, video, audio, or a sticker), such as a .cpp, .txt, .pdf, or .zip file, the LINE Messaging API handles it using the generic FileMessage type.
    3. The event object contains specific properties that allow you to identify and retrieve the file:
      • id (The value is a string like '594382346082582836'.)
      • type (The value is always a string 'file'.)
      • file_name (The original name of the uploaded file.)
      • file_size (in bytes)
    4. For image messages, the type is 'image', and it does not have properties 'file_name' or 'file_size'.
    from flask import Flask, request, abort
    import os
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, FileMessage,
        TextSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text='Please upload a file.'))
    
    STATIC_DIR = 'static/user_uploads'
    os.makedirs(STATIC_DIR, exist_ok=True)
    
    @handler.add(MessageEvent, message=FileMessage)
    def handle_uploaded_file(event):
        message_id = event.message.id
        file_name = event.message.file_name # e.g., 'main.cpp'
        file_size = event.message.file_size
        message_content = line_bot_api.get_message_content(message_id)
        file_path = os.path.join(STATIC_DIR, f'{file_name}')
        with open(file_path, 'wb') as f:
            # Iterate over the content in chunks and write to the file
            for chunk in message_content.iter_content():
                f.write(chunk)
        print(f"File successfully saved at: {file_path} ({file_size} bytes)")

    Flex Messages

    1. Flex Messages are messages that offer an extensive and interactive layout compared to ordinary LINE messages. Ordinary LINE messages deliver only a single source type, such as text, image, and video.
    2. Flex Messages have a hierarchical structure for building blocks, in three levels. The top level is container, followed by blocks (header, hero, body, footer) and then components.
      flex hierarchy
    3. Components in a Flex Message is defined in JSON. The following is a simple example for "Hello World".
      from flask import Flask, request, abort
      import os
      import json
      
      from linebot import (
          LineBotApi, WebhookHandler
      )
      from linebot.exceptions import (
          InvalidSignatureError
      )
      from linebot.models import (
          MessageEvent, TextMessage, ImageMessage,
          TextSendMessage, ImageSendMessage, FlexSendMessage,
      )
      
      app = Flask(__name__)
      
      # Channel access token
      CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
      line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
      # Channel Secret
      CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
      handler = WebhookHandler(CHANNEL_SECRET)
      
      @app.route("/callback", methods=['POST'])
      def callback():
          # get X-Line-Signature header value
          signature = request.headers['X-Line-Signature']
          # get request body as text
          body = request.get_data(as_text=True)
          app.logger.info("Request body: " + body)
          # handle webhook body
          try:
              handler.handle(body, signature)
          except InvalidSignatureError:
              print("Invalid signature. Please check your channel access token/channel secret.")
              abort(400)
          return 'OK'
      
      @handler.add(MessageEvent, message=TextMessage)
      def handle_message(event):
          msg = event.message.text
          flex_json_string = r'''{
            "type": "bubble",
            "body": {
              "type": "box",
              "layout": "horizontal",
              "contents": [
                {
                  "type": "text",
                  "text": "Hello,"
                },
                {
                  "type": "text",
                  "text": "World!"
                }
              ]
            }
          }'''
          flex_msg = FlexSendMessage(
              alt_text='Flex Message',
              contents=json.loads(flex_json_string))
          line_bot_api.reply_message(
                  event.reply_token,
                  flex_msg)

    Flex Hierarchy

    from flask import Flask, request, abort
    import os
    import json
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, ImageMessage,
        TextSendMessage, ImageSendMessage, FlexSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        flex_json_string = r'''{
          "type": "bubble",
          "header": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "ORDER CONFIRMED"
                }
              ],
              "backgroundColor": "#27ae60"
          },
          "hero": {
            "type": "image",
            "url": "https://ipv6.ncnu.org/Images/ncnu_logo-100x100.png",
            "size": "full",
            "aspectRatio": "2:1"
          },
          "body": {
            "type": "box",
            "layout": "horizontal",
            "contents": [
              {
                "type": "text",
                "text": "Hello."
              },
              {
                "type": "text",
                "text": "Thanks for your purchase.",
                "wrap": true
              }
            ]
          },
          "footer": {
          "type": "box",
            "layout": "horizontal",
            "contents": [
              {
                "type": "button",
                "style": "primary",
                "action": {
                  "type": "message",
                  "label": "YES",
                  "text": "Yes"
                }
              },
              {
                "type": "button",
                "style": "secondary",
                "action": {
                  "type": "message",
                  "label": "NO",
                  "text": "No"
                }
              },
              {
                "type": "button",
                "style": "link",
                "action": {
                  "type": "uri",
                  "label": "CNN",
                  "uri": "https://cnn.com/"
                }
              }
            ]
          }
        }'''
        flex_msg = FlexSendMessage(
            alt_text='Flex Message',
            contents=json.loads(flex_json_string))
        line_bot_api.reply_message(
                event.reply_token,
                flex_msg)

    Carousel


    [carousel]
    
    from flask import Flask, request, abort
    import os
    import json
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, TextSendMessage, ImageSendMessage,
        FlexSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
    
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
    
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
    
        return 'OK'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        flex_json_string = r'''{
          "type": "carousel",
          "contents": [
            {
              "type": "bubble",
              "body": {
                "type": "box",
                "layout": "horizontal",
                "contents": [
                  {
                    "type": "text",
                    "text": "Alice",
                    "color": "#ff0000"
                  }
                ]
              }
            },
            {
              "type": "bubble",
              "body": {
                "type": "box",
                "layout": "horizontal",
                "contents": [
                  {
                    "type": "text",
                    "text": "Bob",
                    "color": "#00ff00"
                  }
                ]
              }
            },
            {
              "type": "bubble",
              "body": {
                "type": "box",
                "layout": "horizontal",
                "contents": [
                  {
                    "type": "text",
                    "text": "Carol",
                    "color": "#0000ff"
                  }
                ]
              }
            },
            {
              "type": "bubble",
              "body": {
                "type": "box",
                "layout": "horizontal",
                "contents": [
                  {
                    "type": "text",
                    "text": "Daniel",
                    "color": "#00ff00"
                  }
                ]
              }
            }
          ]
    }
        '''
        flex_message = FlexSendMessage(
            alt_text="Charlie's Angels",
            contents=json.loads(flex_json_string))
        line_bot_api.reply_message(
            event.reply_token,
            flex_message)
    
    

    Button

    More actions can be found here. I think the most useful ones are
    1. Postback
    2. Message
    3. URI
    from flask import Flask, request, abort
    import os
    import json
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, TextMessage, ImageMessage,
        TextSendMessage, ImageSendMessage, FlexSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        flex_json_string = r'''{
          "type": "bubble",
          "header": {
              "type": "box",
              "layout": "vertical",
              "contents": [
                {
                  "type": "text",
                  "text": "ORDER CONFIRMED"
                }
              ],
              "backgroundColor": "#27ae60"
          },
          "hero": {
            "type": "image",
            "url": "https://ipv6.ncnu.org/Images/ncnu_logo-100x100.png",
            "size": "full",
            "aspectRatio": "2:1"
          },
          "body": {
            "type": "box",
            "layout": "horizontal",
            "contents": [
              {
                "type": "text",
                "text": "Hello."
              },
              {
                "type": "text",
                "text": "Thanks for your purchase.",
                "wrap": true
              }
            ]
          },
          "footer": {
            "type": "box",
            "layout": "horizontal",
            "contents": [
              {
                "type": "button",
                "style": "primary",
                "action": {
                  "type": "message",
                  "label": "YES",
                  "text": "Yes"
                }
              },
              {
                "type": "button",
                "style": "secondary",
                "action": {
                  "type": "message",
                  "label": "NO",
                  "text": "No"
                }
              },
              {
                "type": "button",
                "style": "link",
                "action": {
                  "type": "uri",
                  "label": "CNN",
                  "uri": "https://cnn.com/"
                }
              }
            ]
          }
        }'''
        flex_msg = FlexSendMessage(
            alt_text='Flex Message',
            contents=json.loads(flex_json_string))
        line_bot_api.reply_message(
                event.reply_token,
                flex_msg)

    Postback

    from flask import Flask, request, abort
    import os
    import json
    
    from linebot import (
        LineBotApi, WebhookHandler
    )
    from linebot.exceptions import (
        InvalidSignatureError
    )
    from linebot.models import (
        MessageEvent, PostbackEvent, TextMessage, ImageMessage,
        TextSendMessage, ImageSendMessage, FlexSendMessage,
    )
    
    app = Flask(__name__)
    
    # Channel access token
    CHANNEL_ACCESS_TOKEN=os.getenv('CHANNEL_ACCESS_TOKEN')
    line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
    # Channel Secret
    CHANNEL_SECRET = os.getenv('CHANNEL_SECRET')
    handler = WebhookHandler(CHANNEL_SECRET)
    
    @app.route("/callback", methods=['POST'])
    def callback():
        # get X-Line-Signature header value
        signature = request.headers['X-Line-Signature']
        # get request body as text
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        # handle webhook body
        try:
            handler.handle(body, signature)
        except InvalidSignatureError:
            print("Invalid signature. Please check your channel access token/channel secret.")
            abort(400)
        return 'OK'
    
    @handler.add(PostbackEvent)
    def handle_postback(event):
        ''' Postback allows you to send data to your Flask program
            without starting a web browser. '''
        postback_data = event.postback.data  # ← Here: "sid=123&answer=yes"
        # Parse the data (e.g., as query parameters)
        import urllib.parse
        params = urllib.parse.parse_qs(postback_data)
        sid = params.get('sid', [''])[0]
        answer = params.get('answer', [''])[0] 
        print(sid, answer)
        #reply_text = f"Received postback: {postback_data}"
        #print(reply_text)
        #line_bot_api.reply_message(
        #    event.reply_token,
        #    TextSendMessage(text=reply_text)
        #)
    
    @handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        msg = event.message.text
        flex_json_string = r'''{
          "type": "bubble", 
          "footer": {
            "type": "box",
            "layout": "vertical",
            "contents": [
              {
                "type": "button",
                "style": "primary",
                "action": {
                  "type": "postback",
                  "label": "YES",
                  "data": "sid=123&answer=yes",
                  "displayText": "Yes"
                }
              },
              {
                "type": "button",
                "style": "secondary",
                "action": {
                  "type": "postback",
                  "label": "NO",
                  "data": "sid=123&answer=no",
                  "displayText": "No"
                }
              }
            ]
          }
        }'''
        flex_msg = FlexSendMessage(
            alt_text='Flex Message',
            contents=json.loads(flex_json_string))
        line_bot_api.reply_message(
                event.reply_token,
                flex_msg)