** Bold ** is where I had a hard time
I tried to make a bot with Python instead of node.js by referring to the following site. Also, the information obtained by scraping was obtained from the same site as the one listed above.
I made a LINE bot that tells me the waiting time of Disney
Since this site is supposed to be easy to use, there are many character strings, but on the contrary, I used the rich menu and Flex Message so that the user can select the desired data. Rich menus, Flex Messages, etc. are not organized, so I looked at the references and went back and forth between various sites, so I hope to put them together.
I will paste the completed version first. Please register and use it if you like! To read directly, please click here
The source code is available on GitHub. If you want to see detailed scraping code etc., please click here [https://github.com/ryodisney/disney_wait)
I put everything in a folder called disney. Since .git was a hidden file, it does not appear here, but it is in the same hierarchy as deploy.bat.
disney
├ deploy.bat
├ main.py
├ scrape_requests.py
├ makejsonfile.py
├ Procfile
├ runtime.txt
├ requirements.txt
│
└─templates
land_theme.json
recipt.json
sea_theme.json
theme_select.json
The part that moves the LINE bot will be explained later, so the contents of the configuration file are listed below.
** deploy.bat **: This saves you from having to type every command when you deploy a modified version to Heroku.
deploy.bat
git add . && git commit -m 'Improve' && git push
** Procfile **: Creates a configuration file, Procfile, to teach Heroku how to start the program. After moving to the current directory at the command prompt, enter the command below. At this time, put the one that starts first in the place where main.py is written. The name doesn't have to be main.py.
Procfile
echo web: python main.py > Procfile
** runtime.txt **: The version of Python to use is listed here.
runtime.txt
python-3.7.0
** requirements.txt **: The modules used in Python that have been pip installed are written here. Now you can use these modules on Heroku as well.
requirements.txt
Flask==1.1.1
line-bot-sdk==1.15.0
requests==2.21.0
bs4==0.0.1
lxml==4.4.2
https://github.com/heroku/heroku-buildpack-chromedriver.git
https://github.com/heroku/heroku-buildpack-google-chrome.git
The 6-split part shown in the image below is the rich menu. This time, instead of building it yourself, I will use the functions of LINE Official Manager. I really wanted to use a postback, so I wanted to make it completely myself, but I compromised because I couldn't figure out how to implement it even after reading the reference.
LINE Official Manager After logging in, click the red frame below. If you press the create button there, the following page will be displayed. The title can be anything. It seems to distinguish when you make multiple rich menus. (It seems that you can not switch the rich menu with the same account unless you make it yourself) I think that the display period should be set aside longer. The start date will not come out unless it is before the implementation date. (Of course)
Go down to ** Content Settings **. You can select the number of divisions by pressing ** Select template **. I chose 6 divisions here. If you select ** Upload background image **, it will be a one-sided image, so if you want to attach an image individually to 6 divisions, press ** Create image ** below. You can also choose the reaction when you press it with an action. This time it will lead to the event after that, so I will make it a text. (I don't think I have many opportunities to use others) Here are some points when creating an image. First, you can upload the image by pressing the red circle icon. And the outer frame is framed by the blue circle icon. I think it would be better if we couldn't understand the boundaries without this. With the default thickness, there was a gap, so the edge was just right with a max of 5. Also, press ** Apply ** in the upper right corner after all. If you press it in the middle, it will be saved as a single image in the background, and you will not be able to edit it individually. This rich menu is the result.
The home button written in the flow of operation is the Mickey icon at the bottom center. It is responsible for returning the text "Home" when pressed. The other five are all for selecting a category, so they are different from the home button.
After pressing the home button, a button like the one below will appear so that you can select a park. I will roughly describe the processing when the home button is pressed. After this, there is also a postback event, and there is information that I want to save such as which park is selected, so I am using global variables considering the scope of the variable. The process after pressing the home button starts with ** les = "les" **. The important points are the following two points.
--How to display json file --Difference between push and reply
home_button.py
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
global park,genre,area,info_url,target_url,counter,situation
text = event.message.text
userid = event.source.user_id
#At first and reset
if text == "home":
#Initialization
park = "park"
genre = "genre"
area = "area"
info_url = ""
target_url = ""
counter = 0
situation = ""
les = "les"
template = template_env.get_template('theme_select.json')
data = template.render(dict(items=les))
select__theme_massage = FlexSendMessage(
alt_text="Theme selection",
contents=BubbleContainer.new_from_json_dict(json.loads(data))
)
line_bot_api.push_message(userid, messages=select__theme_massage)
The first thing I want to write about is TextMessage and FlexSendMessage. These need to be tampered with at the top of the source code copied and pasted with Echolalia. It seems that Event, Action, Message system needs to be imported as shown below. If you get an error, please check if it is described here.
from linebot.models import (
MessageEvent, TextMessage, PostbackTemplateAction, PostbackEvent, PostbackAction, QuickReplyButton, QuickReply,
FlexSendMessage, BubbleContainer, CarouselContainer, TextSendMessage
)
To be honest, I only know how to apply it to the template using jinja2, so I don't understand much. Therefore, if you are a beginner, it will be faster to copy ** les = "les" ** or less.
I used Flex Message Simulator to mess with the finished product and put it into shape for the time being.
```json:theme_select.json
{
"type": "bubble",
"hero": {
"type": "image",
"url": "https://secured.disney.co.jp/content/disney/jp/secured/dcc/tokuten/bf-tdr-prk-tckt/_jcr_content/par/dcc_hero_panel_image/image1.img.jpg/1474355301452.jpg ",
"size": "full",
"aspectRatio": "20:13",
"aspectMode": "cover"
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Please select a park",
"weight": "bold",
"size": "lg"
}
]
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "postback",
"label": "land",
"data": "land"
}
},
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "postback",
"label": "C",
"data": "sea"
}
},
{
"type": "spacer",
"size": "sm"
}
],
"flex": 0
}
} ``` The content of "action" is - type - label - data
"Type" is the form of data exchange, "label" is what is written on the button (in this case, "land", "sea"), and "data" is the data to be received. It seems that images and sounds are also included in "data". For more information, please read Reference. (Supplement) Select "type" of "action": {} according to the purpose. If you want it to appear as a message when the other party presses it, you should use "type": message.
To save the json file, create a folder called templates in the same hierarchy and save it in that folder! It seems that it is reading from there.
Substitute in the code below Substitute in the character string part.
template = template_env.get_template('theme_select.json')
When implementing a carousel (horizontal slide one), change to the code below Change from Bubble Container to Carousel Container.
select__theme_massage = FlexSendMessage(
alt_text="Theme selection",
contents=CarouselContainer.new_from_json_dict(json.loads(data))
)
You can push multiple times for one event, but reply seems to be possible only once. So if you reply, you will not be able to send any more messages after that. For example, assuming that scraping will take some time, if you want to display "Processing" when receiving a message from the user and then display the result in a json file, push "Processing" and push the json file. It will be solved by replying. Also, push must prepare userid and message. Look at button.py above and imitate it.
push.py
line_bot_api.push_message(userid, messages=select__theme_massage)
reply.py
line_bot_api.reply_message(
event.reply_token,
FlexSendMessage(
alt_text="Result display",
contents=BubbleContainer.new_from_json_dict(json.loads(data))
)
)
The datetime is used to display "closed" except for the opening hours. Below are the two codes. The first is to receive the park selection data in a postback, check the business hours, and reply to the user with the return value. The second is a code that confirms business hours. (bonus)
** Note: ** If you don't set Heroku's time zone to Japan, datetime will be in the US time zone. For time zone setting → this article
postback_park.py
@handler.add(PostbackEvent)
def handle_postback(event):
global park,genre,area,info_url,target_url,counter,situation
area = ""
post_data = event.postback.data
userid = event.source.user_id
if post_data == "land" or post_data == "sea":
park = post_data
if park == "land":
#Links such as opening hours and weather
info_url = "https://tokyodisneyresort.info/index.php?park=land"
park_ja = "land"
elif park == "sea":
#Links such as opening hours and weather
info_url = "https://tokyodisneyresort.info/index.php?park=sea"
park_ja ="C"
#Check opening hours
business_hour = Scrape_day(info_url)
situation = Check_park(business_hour)
if situation == "close":
print("close")
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text="The park is closed")
)
elif situation == "open":
park_message = TextSendMessage(text= str(park_ja) + "Is selected\n categories from the menu below\n Please select")
line_bot_api.push_message(userid, messages=park_message)
Emptying the area is an error avoidance assuming that you accidentally press the button multiple times.
check_park.py
#Check if it's opening time now
def Check_park(business_hour):
#Check the current time and date
dt_now = dt.now()
#Today's date
year = int(dt_now.year)
month = int(dt_now.month)
day = int(dt_now.day)
#Division of opening hours
open_time = business_hour.split("~")[0]
if open_time.split(":")[0] == "":
return "close"
else:
open_hour = int(open_time.split(":")[0])
open_minute = int(open_time.split(":")[1])
#Division of closing time
close_time = business_hour.split("~")[1]
close_hour = int(close_time.split(":")[0])
close_minute = int(close_time.split(":")[1])
#datetime
open_datetime = dt(year,month,day,open_hour,open_minute)
close_datetime = dt(year,month,day,close_hour,close_minute)
if open_datetime < dt_now < close_datetime:
return "open"
else:
return "close"
The argument business_hour is a string of business hours scraped from the site.
As a result of selecting a park, if it is open, proceed to the next step. Press the rich menu you just created and select which category you want to see the latency. At this time, the selected category is displayed as text on the screen. The only way to avoid this is to make your own rich menu.
As mentioned above, the entire code is published on github, so I will not describe scraping the waiting time in particular. This section describes how to use Flex Message's recipe when outputting the acquired value. If the number of strings to be output is small or if you don't care about the shape, the push or reply described above is sufficient, so you can skip this step.
I will play with the json file this time as well, but I will do three main things.
--Embed variable in part --Embed items that change from time to time --File initialization
I edited the recipe of Flex Message Simulator and created the following json file.
recipt.json
{
"type": "bubble",
"styles": {
"footer": {
"separator": true
}
},
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "Waiting time",
"weight": "bold",
"color": "#1DB446",
"size": "sm"
},
{
"type": "text",
"text": "theme",
"weight": "bold",
"size": "xl",
"margin": "md"
},
{
"type": "separator",
"margin": "xxl"
},
{
"type": "box",
"layout": "vertical",
"margin": "xxl",
"spacing": "sm",
"contents": [
]
}
]
}
}
I want to change the part that says "text": "theme" to "text": "acquired characters", so perform the following processing.
set_json.py
def Send_area(area):
json_file = open('templates/recipt.json', 'r',encoding="utf-8-sig")
json_object = json.load(json_file)
json_object["body"]["contents"][1]["text"] = str(area)
#writing
new_json_file = open('templates/recipt.json', 'w',encoding="utf-8")
json.dump(json_object, new_json_file, indent=2,ensure_ascii=False)
As a procedure
This was solved by embedding a variable in a box with a fixed shape and inserting it additionally.
new_json.py
def Make_jsonfile(attraction,info):
json_file = open('templates/recipt.json', 'r',encoding="utf-8-sig")
json_object = json.load(json_file)
new = {
"type": "box",
"layout": "vertical",
"margin": "xxl",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "text",
"text": str(attraction),
"size": "sm",
"color": "#555555",
"flex": 0
},
{
"type": "text",
"text": str(info),
"size": "md",
"color": "#111111",
"align": "end"
}
]
}
]
}
json_object["body"]["contents"][3]["contents"].append(new)
new_json_file = open('templates/recipt.json', 'w',encoding="utf-8")
json.dump(json_object, new_json_file, indent=2,ensure_ascii=False)
It's complicated and difficult to understand, but the important thing is to append (new). The empty contents at the bottom of the above recipe.json is in list format, so you can append to it even if you don't know the number of data to display in advance. In new_json.py, the attraction name and waiting time are embedded in variables, and a box that summarizes them is inserted in contents.
Encoding is done with utf-8-sig to avoid errors. If you have an error in utf-8, please see this site → UnicodeDecodeError: When'cp932'appears
If Heroku's log check says empty, this is probably the cause. Since the original contents are empty, I get an error when printing if nothing has been added. First, let's check if box is added to contents.
Of course, if you repeat append without initializing, the information so far will remain. Before creating the recipt, it is initialized by overwriting the above recipt.json.
Although it was my first post, it has become quite long. This time, I focused on the rich menu and Flex Message that I had a hard time, and I hope it will be helpful to someone. The challenges that can be raised are
--If you can get information from the official website, more items can be displayed (though I think it is strict in terms of security). ――I want to create my own rich menu and use the rich menu itself dynamically (when I press the rich menu, a new rich menu appears instead of a button)
there is. In particular, there are few Python articles about the second rich menu, so if anyone who happens to read this article is familiar with it, I would be grateful if you could post it.
Recommended Posts