I created a linebot that returns a date selection action when you send an image and draws the selected date on the image. Basically, I just made it possible to select the date part of this article by myself, but since there were various stumbling points, I will summarize it this time. Reference: [Python] I made a LINE Bot that dates photos
--LineBot channel creation --Deploy to heroku
main.py
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (PostbackEvent, TemplateSendMessage, ButtonsTemplate, DatetimePickerTemplateAction,
ImageMessage, ImageSendMessage, MessageEvent, TextMessage, TextSendMessage)
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import datetime
import os
import re
app = Flask(__name__)
app.debug = False
#Get environment variables
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
#Image referrer path
SRC_IMAGE_PATH = "static/images/{}.jpg "
MAIN_IMAGE_PATH = "static/images/{}_main.jpg "
PREVIEW_IMAGE_PATH = "static/images/{}_preview.jpg "
@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:
abort(400)
return 'OK'
#Follow event
@handler.add(FollowEvent)
def handle_follow(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=
"Thank you for registering as a friend. If you send an image and tell me the shooting date, I will write that date on the image"))
#Text parrot return
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text))
#Receiving images
@handler.add(MessageEvent, message=ImageMessage)
def get_image(event):
global message_id
#message_Get id
message_id = event.message.id
#File name message_Path converted to id
src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
#Temporarily save images to heroku
save_image(message_id, src_image_path)
#Save as an image to display when selecting the date and time
im = Image.open(src_image_path)
im.save(src_image_path)
#Selection of shooting date
date_picker = TemplateSendMessage(
alt_text='Please select the shooting date',
template=ButtonsTemplate(
text='Please select the shooting date',
thumbnail_image_url=f"https://<heroku app name>.herokuapp.com/{src_image_path}",
actions=[
DatetimePickerTemplateAction(
label='Choice',
data='action=buy&itemid=1',
mode='date',
initial=str(datetime.date.today()),
max=str(datetime.date.today())
)
]
)
)
line_bot_api.reply_message(
event.reply_token,
date_picker
)
#Process and send images
@handler.add(PostbackEvent)
def handle_postback(event):
#File name message_Path converted to id
src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
main_image_path = MAIN_IMAGE_PATH.format(message_id)
preview_image_path = PREVIEW_IMAGE_PATH.format(message_id)
#Image processing
date_the_image(src_image_path, Path(main_image_path).absolute(), event)
date_the_image(src_image_path, Path(preview_image_path).absolute(), event)
#Send image
image_message = ImageSendMessage(
original_content_url=f"https://<heroku app name>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<heroku app name>.herokuapp.com/{preview_image_path}"
)
#Get log
app.logger.info(f"https://<heroku app name>.herokuapp.com/{main_image_path}")
line_bot_api.reply_message(event.reply_token, image_message)
#Image storage function
def save_image(message_id: str, save_path: str) -> None:
# message_Get binary data of image from id
message_content = line_bot_api.get_message_content(message_id)
with open(save_path, "wb") as f:
#Write the acquired binary data
for chunk in message_content.iter_content():
f.write(chunk)
#Image processing function
def date_the_image(src: str, desc: str, event) -> None:
im = Image.open(src)
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("./fonts/Helvetica.ttc", 50)
#Get the date of the date and time selection action
text = event.postback.params['date']
#String replacement with regular expression
text_mod = re.sub("-", "/", text)
#Text size
text_width = draw.textsize(text_mod, font=font)[0]
text_height = draw.textsize(text_mod, font=font)[1]
margin = 10
x = im.width - text_width
y = im.height - text_height
#The size of the rectangle to draw
rect_size = ((text_width + margin * 6), (text_height + margin * 2))
#Rectangle drawing
rect = Image.new("RGB", rect_size, (0, 0, 0))
#Mask to make the rectangle transparent
mask = Image.new("L", rect_size, 128)
#Paste the rectangle and mask on the image
im.paste(rect, (x - margin * 6, y - margin * 3), mask)
#Writing text
draw.text((x - margin * 3, y - margin * 2), text_mod, fill=(255, 255, 255), font=font)
im.save(desc)
if __name__ == "__main__":
#app.run()
port = int(os.getenv("PORT", 5000))
app.run(host="0.0.0.0", port=port)
from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (PostbackEvent, TemplateSendMessage, ButtonsTemplate, DatetimePickerTemplateAction,
ImageMessage, ImageSendMessage, MessageEvent, TextMessage, TextSendMessage)
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import datetime
import os
import re
app = Flask(__name__)
app.debug = False
#Get environment variables
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
#Image referrer path
SRC_IMAGE_PATH = "static/images/{}.jpg "
MAIN_IMAGE_PATH = "static/images/{}_main.jpg "
PREVIEW_IMAGE_PATH = "static/images/{}_preview.jpg "
@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:
abort(400)
return 'OK'
Import the module and get the environment variables set in advance, but I hope you can check the detailed role as appropriate.
Set the path of the image reference source described later and make an empty list to store ~~ message_id
. ~~
Replace the {}
part with the message_id
when the image was received.
#Follow event
@handler.add(FollowEvent)
def handle_follow(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=
"Thank you for registering as a friend. If you send an image and tell me the shooting date, I will write that date on the image"))
A message is sent when a user adds a friend.
#Text parrot return
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=event.message.text))
I will return the text parrot, but it is not necessary.
#Receiving images
@handler.add(MessageEvent, message=ImageMessage)
def get_image(event):
global message_id
#message_Get id
message_id = event.message.id
#File name message_Path converted to id
src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
#Temporarily save images to heroku
save_image(message_id, src_image_path)
#Save as an image to display when selecting the date and time
im = Image.open(src_image_path)
im.save(src_image_path)
#Selection of shooting date
date_picker = TemplateSendMessage(
alt_text='Please select the shooting date',
template=ButtonsTemplate(
text='Please select the shooting date',
thumbnail_image_url=f"https://<heroku app name>.herokuapp.com/{src_image_path}",
actions=[
DatetimePickerTemplateAction(
label='Choice',
data='action=buy&itemid=1',
mode='date',
initial=str(datetime.date.today()),
max=str(datetime.date.today())
)
]
)
)
line_bot_api.reply_message(
event.reply_token,
date_picker
)
ʻEvent.message_idgets the id assigned to each message and sets it globally for use in other events. .. ~~ This id cannot be obtained in other events, so store it in the empty
message_listcreated in advance. ~~ After temporarily saving the data on heroku, open the saved data for display during the date selection action and save it again as an image. When returning the date selection action with
TemplateSendMessage and asking the user to select the shooting date, it will be displayed if you specify the URL of the save destination with
thumbnail_image_url`.
#Process and send images
@handler.add(PostbackEvent)
def handle_postback(event):
#File name message_Path converted to id
src_image_path = Path(SRC_IMAGE_PATH.format(message_id)).absolute()
main_image_path = MAIN_IMAGE_PATH.format(message_id)
preview_image_path = PREVIEW_IMAGE_PATH.format(message_id)
#Image processing
date_the_image(src_image_path, Path(main_image_path).absolute(), event)
date_the_image(src_image_path, Path(preview_image_path).absolute(), event)
#Send image
image_message = ImageSendMessage(
original_content_url=f"https://<heroku app name>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<heroku app name>.herokuapp.com/{preview_image_path}"
)
#Get log
app.logger.info(f"https://<heroku app name>.herokuapp.com/{main_image_path}")
line_bot_api.reply_message(event.reply_token, image_message)
Gets the image received in the MessageEvent (ImageMessage) mentioned above. ~~ Get the id that will be the name of the image from message_list
to determine.
If images are sent continuously, the new id will be stored before the data is initialized, so specify the last (latest) one with message_list [-1]
.
Get the date selected by the user with handl_postback (event)
and assign it to text
of the function data_the_image
to write text etc.
When the process is finished, save the image and send it back to the user to finish.
#Image storage function
def save_image(message_id: str, save_path: str) -> None:
# message_Get binary data of image from id
message_content = line_bot_api.get_message_content(message_id)
with open(save_path, "wb") as f:
#Write the acquired binary data
for chunk in message_content.iter_content():
f.write(chunk)
Actually, I wanted to get the Exif information of the image and automatically enter the shooting date, but I could not get it with this method, so I took the form of the above date selection action as a painstaking measure. If anyone knows how to do it, please let me know.
#Image processing function
def date_the_image(src: str, desc: str, event) -> None:
im = Image.open(src)
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("./fonts/Helvetica.ttc", 50)
#Get the date of the date and time selection action
text = event.postback.params['date']
#String replacement with regular expression
text_mod = re.sub("-", "/", text)
#Text size
text_width = draw.textsize(text_mod, font=font)[0]
text_height = draw.textsize(text_mod, font=font)[1]
margin = 10
x = im.width - text_width
y = im.height - text_height
#The size of the rectangle to draw
rect_size = ((text_width + margin * 6), (text_height + margin * 2))
#Rectangle drawing
rect = Image.new("RGB", rect_size, (0, 0, 0))
#Mask to make the rectangle transparent
mask = Image.new("L", rect_size, 128)
#Paste the rectangle and mask on the image
im.paste(rect, (x - margin * 6, y - margin * 3), mask)
#Writing text
draw.text((x - margin * 3, y - margin * 2), text_mod, fill=(255, 255, 255), font=font)
im.save(desc)
I wanted to make it look good to some extent, so I added some processing to it.
ʻEvent.postback.params ['date']to get the date selected by the user, and
re.sub ("-", "/", text) to change" YYYY-MM-DD "to" YYYY / Convert to the format "MM / DD". Use
((text_width + margin * 6), (text_height + margin * 2))to set the size of the rectangle and mask, and leave a margin of 30px on the left and right and 10px on the top and bottom of the text. Finally, specify the position of the rectangle and mask with ʻim.paste (rect, (x --margin * 6, y --margin * 3), mask)
and paste it, anddraw.text ( Write the text with (x --margin * 3, y --margin * 2), text_mod, fill = (255, 255, 255), font = font)
and you're done.
if __name__ == "__main__":
#app.run()
port = int(os.getenv("PORT", 5000))
app.run(host="0.0.0.0", port=port)
This line bot was the first app I made after self-education, but it took about half a year (about 1 to 2 hours a day on average) to make it so far. About 80% of the production time was spent investigating what I didn't understand, and there were times when it was quite difficult. In fact, there were times when I didn't open my computer for about a week, but I never thought about giving up programming, so I think I was having some fun doing it.
Actually, there were twists and turns from the first thing I wanted to make, and I settled on this shape, but I learned that it is very important to make the whole flow in advance to some extent. However, it is difficult to grasp what kind of work can be done with how much effort with little experience, so in the end I think that there is no choice but to proceed steadily with trial and error.
Currently, I am studying a database, and I am trying to save the name and date of birth of the person in the photo so that I can calculate back from the shooting date and display how old the photo is, so as soon as it is completed I will publish it.
-[Python] I made a LINE Bot that dates photos -Implementation of Datetime picker action using line-bot-sdk-python and implementation sample of Image Carousel -Putalpha to create transparent png image with Python, Pillow
Recommended Posts