line / line-bot-sdk-python

LINE Messaging API SDK for Python
https://pypi.python.org/pypi/line-bot-sdk
Apache License 2.0
1.87k stars 962 forks source link

Question about carousel template send via reply_message method. #641

Open thkaw opened 3 weeks ago

thkaw commented 3 weeks ago

When I reply with carousel template, I found in V3 api won't work, and give folloing error code:

linebot.v3.messaging.exceptions.ApiException: (400) Reason: Bad Request HTTP response headers: HTTPHeaderDict({'Server': 'legy', 'Content-Type': 'application/json', 'x-content-type-options': 'nosniff', 'x-frame-options': 'DENY', 'x-line-request-id': '7842f750-9181-4c54-8b63-c4a9345738ac', 'x-xss-protection': '1; mode=block', 'Content-Length': '186', 'Expires': 'Wed, 05 Jun 2024 07:02:49 GMT', 'Cache-Control': 'max-age=0, no-cache, no-store', 'Pragma': 'no-cache', 'Date': 'Wed, 05 Jun 2024 07:02:49 GMT', 'Connection': 'close'}) HTTP response body: {"message":"The request body has 2 error(s)","details":[{"message":"May not be empty","property":"messages[0].altText"},{"message":"May not be empty","property":"messages[0].template"}]}


To make sure I really set altText & template attribute, I event write the dict sperately.

NG code

def reply_carousel(reply_token, carousel):  #carousel is a TemplateSendMessage object
    carousel_dict = {
        "type": "template",
        "altText": carousel.alt_text,
        "template": {
            "type": "carousel",
            "columns": [
                {
                    "text": column.text,
                    "actions": [
                        {
                            "type": action.type,
                            "label": action.label,
                            "text": action.text
                        } for action in column.actions
                    ]
                } for column in carousel.template.columns
            ]
        }
    }

    messaging_api.reply_message(
        ReplyMessageRequest(
            reply_token=reply_token,
            messages=[carousel_dict]
        )
    )

So, I tried rollback to old api method, that will be fine.

def reply_carousel(reply_token, carousel):
    line_bot_api.reply_message(reply_token, carousel)

Is any limitation between new SDK API and carousel template? Thanks in advance!

Yang-33 commented 3 weeks ago

Thank you for using line-bot-sdk-python, @thkaw !

To better understand the issue, could you provide a minimal yet sufficient and reproducible code example?

For instance, this example should work: https://github.com/line/line-bot-sdk-python/blob/bdda65fd8dbc715e91564a05d146f4c2c0ed9511/examples/flask-kitchensink/app.py#L324-L351

thkaw commented 3 weeks ago

Hi @Yang-33 , Thanks for quick replied.

The example you mention is OK, also I use that as my workaround sofar. However, it will gen following waring log if line_bot_api.reply_message have been invoked

C:\Users\ar801\PycharmProjects\egolden\playground.py:44: LineBotSdkDeprecatedIn30: Call to deprecated method reply_message. (Use 'from linebot.v3.messaging import MessagingApi' and 'MessagingApi(...).reply_message(...)' instead. See https://github.com/line/line-bot-sdk-python/blob/master/README.rst for more details.) -- Deprecated since version 3.0.0. line_bot_api.reply_message(reply_token, carousel)

Here I put the sample for my question, you should able to reproduce problem by using this code snippet

from flask import Flask, request, abort
from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError
from linebot.v3.messaging import Configuration, ApiClient, MessagingApi, ReplyMessageRequest
from linebot.v3.webhooks import MessageEvent, TextMessageContent
from linebot import LineBotApi
from linebot.models import TemplateSendMessage, CarouselTemplate, CarouselColumn, MessageAction

app = Flask(__name__)

ATOKEN = "PUT YOUR ACCESS TOKEN"
configuration = Configuration(
    access_token=ATOKEN)
handler = WebhookHandler('PUT YOUR WEBHOOK CREDENTIAL HERE')

# 初始化 MessagingApi
api_client = ApiClient(configuration=configuration)
messaging_api = MessagingApi(api_client)
line_bot_api = LineBotApi(ATOKEN)

def get_carousel_template_first_use():
    return TemplateSendMessage(
        alt_text='this is a carousel template',
        template=CarouselTemplate(
            columns=[
                CarouselColumn(
                    text='QUESTION',
                    actions=[
                        MessageAction(
                            label='ANS A',
                            text='ANS A'
                        ),
                        MessageAction(
                            label='ANS B',
                            text='ANS B'
                        )
                    ]
                )
            ]
        )
    )

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        app.logger.info("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    user_id = event.source.user_id
    message_text = event.message.text
    reply_carousel(event.reply_token, get_carousel_template_first_use())

# OK case
# def reply_carousel(reply_token, carousel):
#     line_bot_api.reply_message(reply_token, carousel)

# NG case
def reply_carousel(reply_token, carousel):  #carousel is a TemplateSendMessage object
    carousel_dict = {
        "type": "template",
        "altText": carousel.alt_text,
        "template": {
            "type": "carousel",
            "columns": [
                {
                    "text": column.text,
                    "actions": [
                        {
                            "type": action.type,
                            "label": action.label,
                            "text": action.text
                        } for action in column.actions
                    ]
                } for column in carousel.template.columns
            ]
        }
    }

    messaging_api.reply_message(
        ReplyMessageRequest(
            reply_token=reply_token,
            messages=[carousel_dict]
        )
    )
if __name__ == "__main__":
    app.run(host="0.0.0.0")
Yang-33 commented 3 weeks ago

thanks, ok let me check the example. (it takes time)

Note the example I provided uses v3 modules instead of deprecated modules.

(client) https://github.com/line/line-bot-sdk-python/blob/bdda65fd8dbc715e91564a05d146f4c2c0ed9511/examples/flask-kitchensink/app.py#L162-L163

(usage) https://github.com/line/line-bot-sdk-python/blob/bdda65fd8dbc715e91564a05d146f4c2c0ed9511/examples/flask-kitchensink/app.py#L324-L351

Yang-33 commented 2 weeks ago

I think there are two solutions. The v3 model does not assume passing a dictionary to a method. However, each class in the v3 model has a #from_dict, so you can call that. For example,

- messages=[carousel_dict]
+ from linebot.v3.messaging import TemplateMessage
+ messages=[TemplateMessage.from_dict(carousel_dict)]

Another solution is to stop using dictionaries.

as6325400 commented 1 week ago

I'm currently working on a project using the LINE bot SDK for Python, and I've encountered an issue with the ReplyMessageRequest when sending messages using reply_message.

In the process of converting to ReplyMessageRequest, the messages field can contain multiple different types (e.g., TextMessageContent, FlexMessage). While using .from_dict works for sending the messages, the format of the dict varies significantly.

Additionally, when running the example code from flask-kitchensink, I encounter the following error:

pydantic.v1.error_wrappers.ValidationError: 1 validation error for ReplyMessageRequest
messages -> 0
  value is not a valid dict (type=type_error.dict)

Is there a more general way to handle the different message types in ReplyMessageRequest?

Yang-33 commented 1 week ago

Could you specify in which cases the sample fails? I would like a minimal reproducible code that can demonstrate the issue. As I checked this now, no errors occurred in flask-kitchensink as you said. The error message indicates that the data you provided is not a dictionary.

as6325400 commented 1 week ago

Sorry, I discovered that my issue was caused by simultaneously importing

from linebot.v3.messaging import (
    TextMessage
)

and

from linebot.models import *

This led to the error. It has now been resolved. Thank you.

thkaw commented 11 hours ago

I think there are two solutions. The v3 model does not assume passing a dictionary to a method. However, each class in the v3 model has a #from_dict, so you can call that. For example,

- messages=[carousel_dict]
+ from linebot.v3.messaging import TemplateMessage
+ messages=[TemplateMessage.from_dict(carousel_dict)]

Another solution is to stop using dictionaries.

Thank you provide the information.

However, if I try to change carousel into TemplateSendMessage type (linebot.models.template.TemplateSendMessage)

def reply_carousel(reply_token, carousel):
    try:
        # OK
        # line_bot_api.reply_message(reply_token, carousel)

        messaging_api.reply_message(
            ReplyMessageRequest(
                reply_token=reply_token,
                messages=carousel   #NG here
            )
        )

Error shows:

pydantic.v1.error_wrappers.ValidationError: 1 validation error for ReplyMessageRequest messages value is not a valid list (type=type_error.list)

Is any possible to pass TemplateSendMessage type to reply_message function?