《赖田捕手:追加篇》第 32 天:快速回覆 QuickReply 介绍

第 32 天:快速回覆 QuickReply 介绍

对其他人来说也许没什麽,但对他而言这可真是不容易。因为这个男人认为,打从他有记忆以来就这麽相信,某部分的他是用快速回覆按钮 (QuickReplyButton) 做出来的。他唯恐一个错误的回发动作 (PostbackAction) 被触发,然後他将会在她面前分崩离析。

~节录自《聊天机器人的历史:我妈的忧伤》

延伸自系列文 《从LINE BOT到资料视觉化:赖田捕手》

《赖田捕手:追加篇》:

根据可靠的?科学研究,草泥马是一群视觉系的动物。草泥马心理分析权威是这麽说的:「良好而适当的视觉刺激具有稳定草泥马情绪波动,促进正向思考的能力,是培育积极且强韧的草泥马的不二法门」。这应该是个生理影响心理,而後心理又影响生理的最佳例子。那麽这就让人好奇了,到底什麽是良好而适当的视觉刺激呢?答案是,草泥马们相当享受观赏双色打光影像的时刻。
所谓的双色打光,顾名思义,就是在一个主要观赏目标的两侧,分别打上两种不同的单色光,如图一

https://ithelp.ithome.com.tw/upload/images/20201227/20120178yww14fD6kQ.png
图一、双色打光!

身为一个专业的草泥马训练师,我当然义不容辞要为我所饲养的草泥马们打造出最舒适的环境,也就是将所有的图片转为双色打光的图片,供草泥马们惬意的欣赏。这件事说难不难,说简单,好像也没那麽容易。要将所有的图片转为双色打光的图片?那我岂不是一整天忙着修图就饱了,这样根本没时间照顾草泥马啊。幸好我是一个懂得写 LINE BOT 的草泥马训练师。是的,只要写出一个专门将一般图片转为双色打光图片的 LINE BOT,那所有的任务都交给 LINE BOT 就搞定了。是不是很吸引人呢?那麽,接下来请容我娓娓道来,我是如何建构出一个擅长双色打光的 LINE BOT。

参考资料

关於双色打光的概念,鼓励各位读者参考 Logos By Nick影片分享。相当感谢他的热情教学,才有我的双色打光 LINE BOT。

再整理资料夹

工欲善其事,必先利其器。为了完成我们了不起的双色打光 LINE BOT,将程序码好好的分门别类是一件相当重要的工作。在第 31 天当中,我们已经将程序码做了一个大略的分类,把初始化 LINE BOT 的程序码、处理handler相关的程序码、处理route相关的程序码拆开变成了三个档案。档案结构如下:

D:\appendix>tree /f
Folder PATH listing
Volume serial number is 9C33-6XXD
D:.
│   runtime.txt
│   requirements.txt
│   Procfile
│   Alma.py
│
└───app
        __init__.py
        models_for_line.py
        routes.py 

为了要做出功能强大的 LINE BOT,我们会在处理handler相关的任务中写下更多更长更繁琐的程序码。为了保持乾净的程序码,便於继续扩充和维护,我打算把models_for_line.py这个档案中的详细功能拉出来,另外创立几个档案,因此资料夹的档案结构会变成这样:

D:\appendix>tree /f
Folder PATH listing
Volume serial number is 9C33-6XXD
D:.
│   runtime.txt
│   requirements.txt
│   Procfile
│   Alma.py
│
└───app
    │   __init__.py
    │   models_for_line.py
    │   routes.py 
    │
    └───custom_models
            AlmaTalks.py

详细回覆使用者的方法被放到app/custom_models/AlmaTalks.py

  • app/custom_models/AlmaTalks.py
from app import line_bot_api

from linebot.models import TextSendMessage

def default_reply(event):
    name = line_bot_api.get_profile(event.source.user_id).display_name
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=f"Hello {name}!")
        )

而原本的models_for_line.py则剩下:

  • app/models_for_line.py
from app import handler
from app.custom_models import AlmaTalks

from linebot.models import MessageEvent, TextMessage

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    AlmaTalks.default_reply(event)

为什麽选择这麽做呢?因为我希望models_for_line.py这个档案简单一点,让人一目了然。而真正困难而仔细的各种任务就放到app/custom_models底下的不同档案里。

利用 PIL 以及 numpy 完成双色打光

那麽,实际上最核心的双色打光程序码可以怎麽做呢?概念上来说,应该也不难:

  1. 创造一个跟原始图像大小一样的双色渐层图像
  2. 将双色渐层图像与原始图像叠图

两步骤完成!
而 Python 在影像处理以及数据处理方面有非常丰富的资源库,我们可以简单的用 PIL 和 numpy 这两个资源库来完成这个任务。

  1. 创造一个跟原始图像大小一样的双色渐层图像
import numpy as np

from PIL import Image
from PIL import ImageDraw

def sigmoid(x, alpha):
    return 1 /(1 + np.exp(-x * alpha))

def create_gradient_layer(layer_im, first_tone, second_tone, gradient_factor):
    layer_gradient = Image.new('RGB', layer_im.size)
    draw = ImageDraw.Draw(layer_gradient)

    for i in range(layer_im.size[0]):
        value = sigmoid(i - layer_im.size[0] / 2, gradient_factor / layer_im.size[0])
        fill_color = np.array(first_tone) * value + np.array(second_tone) * (1 - value)
        draw.line([(i, 0), (i, layer_im.size[1]-1)], fill=tuple(fill_color.astype('int')))

    return layer_gradient

所谓的渐层,即是色彩由深而浅的变化,於是我们可以先创造一个sigmoid函式来模拟这种变化,并且设计两个参数,xalphax与位置有关,根据位置来做出不同颜色的深浅变化。而第二个参数alpha则让我们可以控制渐层变化的幅度。以我们设计的sigmoid函式来说,alpha越小,梯度变化越平缓,而alpha越大,梯度变化越急遽,具体结果可以用matplotlib这个用来作图的资源库来检视 (图二):

import matplotlib.pyplot as plt

fig, axes = plt.subplots(3, 1, figsize=(10, 10))

x = np.linspace(0, 10, 101)

for ax, g in zip(axes.flatten(), [1, 10, 100]):
    ax.plot(x, sigmoid(x-5, g), label=f'gradient={g}')
    ax.legend(fontsize='x-large')

https://ithelp.ithome.com.tw/upload/images/20201227/2012017853yNfsANT4.png
图二sigmoid函式运作方式

create_gradient_layer这个函式里,我们先用Image.new()来产生一张新的图像,接着再用ImageDraw.Draw()为这张新的图像上色。

  • layer_gradient = Image.new('RGB', layer_im.size)
    产生一张以'RGB'做为资料储存格式的图像layer_gradient,图像大小则参考原始图像layer_im.size

  • draw = ImageDraw.Draw(layer_gradient)
    准备在新的图像layer_gradient上面作画。

  • draw.line([(i, 0), (i, layer_im.size[1]-1)], fill=tuple(fill_color.astype('int')))
    从座标(i, 0)开始到座标(i, layer_im.size[1]-1)为止,画一条线,该线条的颜色则由fill=tuple(fill_color.astype('int'))来定义。由於我们的layer_gradient这个新图像是用'RGB'做为资料储存的格式,因此fill也要用相同的形式来表示,如红色应该表示为(255, 0, 0),绿色是(0, 255, 0),诸如此类。

这边假定有一张demo_image.jpg,那麽我们执行create_ gradient_layer

create_gradient_layer(Image.open('demo_image.jpg'), (0, 255, 0), (0, 0, 255), 1)

https://ithelp.ithome.com.tw/upload/images/20201227/20120178IBSLj0VtLI.png
图三gradient_factor=1创造出来的双色渐层图像

create_gradient_layer(Image.open('demo_image.jpg'), (0, 255, 0), (0, 0, 255), 10)

https://ithelp.ithome.com.tw/upload/images/20201227/20120178BBDOSiJsbm.png
图四gradient_factor=10创造出来的双色渐层图像

create_gradient_layer(Image.open('demo_image.jpg'), (0, 255, 0), (0, 0, 255), 100)

https://ithelp.ithome.com.tw/upload/images/20201227/20120178Afa48102JY.png
图五gradient_factor=100创造出来的双色渐层图像

  1. 将双色渐层图像与原始图像叠图
    这边则可以用 PIL 提供的blend或是composite两种方式将双色渐层图像与原始图像进行叠图。
  • Image.blend
Image.blend(layer_im, layer_gradient, alpha=0.5)

这边的alpha可以调整两张影像是用何种比例进行叠图的。若希望第一张影像layer_im所占的比例高一些,则alpha要小一点,若希望第二张影像layer_gradient所占的比例高一些,则alpha就设定大一点。极端一点来说,若alpha=0则会直接得到第一张影像layer_im,而alpha=1会直接得到第二张影像layer_gradient

https://ithelp.ithome.com.tw/upload/images/20201227/20120178bAzTxpbYBE.png
图六Image.blend叠图

  • Image.composite
# 用layer_im当滤镜,深色的地方双色打光效果明显
Image.composite(layer_im, layer_gradient, layer_im.convert('L'))

# 用ImageOps.invert(layer_im)当滤镜,浅色的地方双色打光效果明显
Image.composite(layer_im, layer_gradient, ImageOps.invert(layer_im).convert('L'))

第二种方式composite则提供了有趣的叠图选择。一样是将两张影像layer_imlayer_gradient叠在一起,不过第二张影像要透过一个带有透明讯息的滤镜来叠图。而最直觉的滤镜,就是layer_im.convert('L'),这样可以做出如图七的影像,深色的地方会有较强的双色打光效果。

https://ithelp.ithome.com.tw/upload/images/20201227/20120178FSW6eapsHv.png
图七Image.composite叠图

另外我们也可以用ImageOps.invert,将影像黑白反转,这样的滤镜可以做出如图八的影像,浅色的地方会有较强的双色打光效果。

https://ithelp.ithome.com.tw/upload/images/20201227/20120178s6NQzvctvA.png
图八Image.composite加上ImageOps.invert叠图

完成之後,我们可以把所有程序码串在一起了:

import numpy as np
from PIL import Image
from PIL import ImageDraw
from PIL import ImageOps

def sigmoid(x, alpha):
    return 1 /(1 + np.exp(-x * alpha))

def create_gradient_layer(layer_im, first_tone, second_tone, gradient_factor):
    layer_gradient = Image.new('RGB', layer_im.size)
    draw = ImageDraw.Draw(layer_gradient)

    for i in range(layer_im.size[0]):
        value = sigmoid(i - layer_im.size[0] / 2, gradient_factor / layer_im.size[0])
        fill_color = np.array(first_tone) * value + np.array(second_tone) * (1 - value)
        draw.line([(i, 0), (i, layer_im.size[1]-1)], fill=tuple(fill_color.astype('int')))

    return layer_gradient

def dual_tone_run(file, mode, gradient_factor, first_tone, second_tone):
    layer_im = Image.open(file).convert('RGBA')
    layer_gradient = create_gradient_layer(layer_im, first_tone, second_tone, gradient_factor).convert('RGBA')
    
    if mode == 'blend':
        dual_tone = Image.blend(layer_im, layer_gradient, 0.5)
    elif mode == 'composite':
        dual_tone = Image.composite(layer_im, layer_gradient, layer_im.convert('L'))
    elif mode == 'composite_invert':
        dual_tone = Image.composite(layer_im, layer_gradient, ImageOps.invert(layer_im.convert('L')).convert('L'))
    return dual_tone

快速回覆

完成了双色打光的运作之後,就可以开始来设计我们的 LINE BOT 了。大家在实作双色打光的程序码时,应该注意到这段程序码其实拥有许多参数,而这些参数都可以大大的影响最後产生出来的图像。因此我们在建构这个影像处理 LINE BOT 的时候,其实是可以给出不少选项供使用者依照个人偏好或是情境做出选择的。而在这方面,LINE 相当贴心的推出了各种功能,让使用者可以更容易的与 LINE BOT 进行互动,像我们今天要介绍的快速回覆 (QuickReply) 就是其中一种。
快速回覆有点像 LINE BOT 丢给使用者的选择题。LINE BOT 提供选项,而使用者从选择其中一个作为回答,回传给 LINE BOT,完成一次互动,操作起来简单明了。不过,快速回覆最大的缺点是只会显示在手机介面上。因此目前没办法在电脑版的介面上透过快速回覆与 LINT BOT 进行互动。
要使用快速回覆也不难,先看一段 LINE 官方给出的使用范例:

text_message = TextSendMessage(
    text='Hello, world',
    quick_reply=QuickReply(items=[
        QuickReplyButton(action=MessageAction(label="label", text="text"))
    ])
)

这段操作的结果是,LINE BOT 会传送文字讯息'Hello, world'给使用者,而使用者会看到一个快速回覆按钮 (QuickReplyButton)。如果我们想要提供更多的快速回覆按钮,可以这麽做:

quick_reply=QuickReply(
    items=[
        QuickReplyButton(),
        QuickReplyButton(),
        QuickReplyButton(),
        …
    ]
)

只要在代表选项的items这个清单当中放入更多的QuickReplyButton就可以了。
回到我们的范例。这边所提供的快速回覆按钮的文字显示为"label"。使用者可以点击这个按钮,当作对 LINE BOT 的回答。点下去的时候,该按钮所设定的动作被触发,也就是讯息动作MessageAction(label="label", text="text"),这时候系统会为使用者传送文字讯息"text"给 LINE BOT。换句话说,点击带有MessageAction(label="label", text="text")动作的按钮,就好像使用者亲自发送文字讯息"text"给 LINE BOT 一样。
常用的几种动作包括MessageActionPostbackActionURIAction等。回发动作PostbackAction可以看做进阶的MessageAction,除了可以发送文字讯息之外,还会多传送隐藏的资料 (不会显示在 LINE 的对话视窗当中),方便 LINE BOT 根据这些资料做出适当的回应。而URIAction则是为使用者开启指定连结的动作。
这边我打算用回发动作PostbackAction,使用范例如下:

action=PostbackAction(
           label='postback',
           display_text='postback text',
           data='action=buy&itemid=1'
       )
  • label='postback'
    用来设定代表触发该动作的按钮的显示文字。

  • display_text='postback text'
    触发该动作之後,系统为使用者传送的文字讯息。也就是说,点击此按钮,就好像使用者向 LINE BOT 传送了'postback text'这样的文字讯息。但跟MessageAction不同的是,LINE BOT 并不会因此收到MessageEvent。相对的,LINE BOT 会收到的是回发事件PostbackEvent,因为这可是PostbackAction啊。所以说,跟MessageAction不同,这边的文字讯息只是假的,是显示文字 (display_text) 而已。

  • data='action=buy&itemid=1'
    这个才是PostbackAction真正传送到 LINE BOT 的资讯。LINE BOT 会收到PostbackEvent,而我们可以藉由event.postback.data来拿到这些资讯。

好的,既然我们已经知道怎麽做出双色打光,也了解怎麽使用QuickReply,那现在就可以开始来规划一下整个 LINE BOT 跟使用者的互动流程,看看我们的 LINE BOT 怎麽替使用者客制化作出双色打光影像处理。
这部分大家当然可以自由发挥,我就提一个简单的流程来说明我会如何架构:

https://ithelp.ithome.com.tw/upload/images/20201227/20120178oA4b5N3BNY.png
图九、LINE BOT 与使用者互动流程

图九是我打算采用的流程概念图,整个互动从使用者向 LINE BOT 传送图像开始,也就是当 LINE BOT 接收到ImageEvent,整段流程就开始了。使用者依序设定好模式 (mode)、梯度 (gradient_factor)、第一种颜色 (first_tone)、以及第二种颜色 (second_tone),接着 LINE BOT 就根据这些使用者给出的条件,去对使用者一开始传送过来的图像做影像处理。按照这个规划,我们就得为models_for_line.py这个档案添加几段程序码:

  • app/models_for_line.py
from app import handler
from app.custom_models import AlmaTalks

from linebot.models import ImageMessage, PostbackEvent

@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    AlmaTalks.phase_start(event)

@handler.add(PostbackEvent)
def handle_postback(event):
    if not event.postback.data.startswith('second_tone='):
        AlmaTalks.phase_intermediate(event)
    else:
        AlmaTalks.phase_finish(event)

按照这样的设计,当使用者向 LINE BOT 传送图片 (ImageMessage),任务开始,启动AlmaTalks.phase_start这个函式。接着 LINE BOT 会依序接收到使用者透过QuickReplyButton传送过来的PostbackEvent,而这些就交给AlmaTalks.phase_intermediateAlmaTalks.phase_finish来处理。所以现在我们就要来着手撰写这几个函式。

  • app/custom_models/AlmaTalks.py
def phase_start(event):
    # 初始化表格
    CallDatabase.init_table()

    # 检查使用者资料是否存在
    if CallDatabase.check_record(event.source.user_id):
        _ = CallDatabase.update_record(event.source.user_id, 'message_id', event.message.id)
    else:
        _ = CallDatabase.init_record(event.source.user_id, event.message.id)

    mode_dict = {'blend': '线性叠图', 'composite': '滤镜叠图', 'composite_invert': '反式滤镜叠图'}
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(
            text=f"[1/4] 今晚,我想来点双色打光!\n请选择双色打光模式:", 
            quick_reply=QuickReply(
                items=[QuickReplyButton(action=PostbackAction(
                    label=v, 
                    display_text=f'打光模式:{v}',
                    data=f'mode={k}')) for k, v in mode_dict.items()
                ]
            )
        )
    )

为了让 LINE BOT 能够记得使用者选择的设定,我们在第 31 天添加了一个扩充元件 Heroku Postgres 当作资料库,将我们需要保留的资料,也就是使用者的设定,储存起来。所有与资料库的互动,包括初始化表格、检查资料、放入资料、更新资料等等,我都打算放进另一个档案里,也就是app/custom_models/CallDatabase.py,等等会再详细介绍。
根据这段程序码,函式phase_start要做的事就是当接收到使用者传来的图片时,为使用者在资料库中的表格初始化一笔资料 (或是更新资料),接着透过QuickReplyButton提供不同的打光模式选择给使用者。

  • app/custom_models/AlmaTalks.py
def phase_intermediate(event):

    color_dict = {
        'red': '红',
        'orange': '橙',
        'yellow': '黄',
        'green': '绿',
        'blue': '蓝',
        'purple': '紫'
    }
                  
    reply_dict = {
        'mode': '[2/4] 今晚,继续来点双色打光!\n请选择色彩变化梯度:',
        'gradient_factor': '[3/4] 今晚,还想来点双色打光!\n请选择第一道色彩:',
        'first_tone': '[4/4] 今晚,最後来点双色打光!\n请选择第二道色彩:'
    }
    
    quick_button_dict = {
        'mode': 
        [QuickReplyButton(
            action=PostbackAction(
                label=i, 
                display_text=f'变化梯度:{i}', 
                data=f'gradient_factor={i}')) for i in (5, 10, 50, 100)
        ],
        'gradient_factor': 
        [QuickReplyButton(
            action=PostbackAction(
                label=j, 
                display_text=f'第一道色彩:{j}', 
                data=f'first_tone={i}')) for i, j in color_dict.items()
        ],
        'first_tone':
        [QuickReplyButton(
            action=PostbackAction(
                label=j, 
                display_text=f'第二道色彩:{j}', 
                data=f'second_tone={i}')) for i, j in color_dict.items()
        ]
    }
    
    user_id = event.source.user_id
    postback_data = event.postback.data
    current_phase = postback_data.split('=')[0]

    # 依照使用者的选择更新资料
    CallDatabase.update_record(user_id, current_phase, postback_data.split('=')[1])
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(
            text=reply_dict[current_phase],
            quick_reply=QuickReply(
                items=quick_button_dict[current_phase]))
        )

这边应该也难不倒大家。先建构好reply_dictquick_button_dict,按照流程准备好不同阶段相对应的回答跟快速回覆按钮。这边我在QuickReplyButtonPostbackAction里藏了不同阶段的暗示,让 LINE BOT 在收到PostbackEvent时,可以藉由event.postback.data来判断这一连串的互动是进行到哪一阶段了。

  • app/custom_models/AlmaTalks.py
def phase_finish(event):
    user_id = event.source.user_id
    postback_data = event.postback.data
    current_phase = postback_data.split('=')[0]

    # 更新资料并取得最後的完整设定
    record = CallDatabase.update_record(user_id, current_phase, postback_data.split('=')[1])

    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=str(record))
    )

最後一个阶段,在使用者选择好第二道色彩的同时,我们让 LINE BOT 更新这一笔设定,并从资料库中提取该名使用者先前的所有设定,包括打光模式、变化梯度、以及选择的两种色彩。我们可以简单的用TextSendMessage来将设定的内容传回给使用者,检查 LINE BOT 是否真的记下了这些内容。

连接 Heroku Postgres

在前面我们设计的互动过程中,LINE BOT 是利用app/custom_models/CallDatabase.py来操作 Heroku Postgres,纪录、更新、提取使用者的设定。在互动流程大致底定之後,现在该是时候把CallDatabase给生出来了。

  • app/custom_models/CallDatabase.py
import os
import psycopg2

def access_database():
    DATABASE_URL = os.environ['DATABASE_URL']
    conn = psycopg2.connect(DATABASE_URL, sslmode='require')
    cursor = conn.cursor()
    return conn, cursor

def init_table():
    conn, cursor = access_database()
    postgres_table_query = "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema'"
    cursor.execute(postgres_table_query)
    table_records = cursor.fetchall()
    table_records = [i[0] for i in table_records]

    if 'user_dualtone_settings' not in table_records:
        create_table_query = """CREATE TABLE user_dualtone_settings (
            user_id VARCHAR ( 50 ) PRIMARY KEY,
            message_id VARCHAR ( 50 ) NOT NULL,
            mode VARCHAR ( 20 ) NOT NULL,
            gradient_factor VARCHAR ( 20 ) NOT NULL,
            first_tone VARCHAR ( 20 ) NOT NULL,
            second_tone VARCHAR ( 20 ) NOT NULL
        );"""

        cursor.execute(create_table_query)
        conn.commit()

    return True

先写好两个函式:连接 Heroku Postgres 资料库的起手式access_database,以及在资料库中初始化表格init_table。先跟大家道个歉。在第 31 天当中,我也写了一个init_table这个函式,用来创造我们需要用的表格'user_dualtone_settings'。不过经过一个星期的修订之後,我想要更改表格的栏位和资料类型,改动如下:

CREATE TABLE user_dualtone_settings (
    user_id VARCHAR ( 50 ) PRIMARY KEY,
    message_id VARCHAR ( 50 ) NOT NULL,
    mode VARCHAR ( 20 ) NOT NULL,
    gradient_factor VARCHAR ( 20 ) NOT NULL,
    first_tone VARCHAR ( 20 ) NOT NULL,
    second_tone VARCHAR ( 20 ) NOT NULL
);

所以说,如果有人已经根据上星期的内容在资料库里新增了一个表格,那较简单的方法可能是把 Heroku Postgres 这个扩充元件给删了,再重新新增一个。当然,如果对 SQL 语法以及psycopg2熟悉的朋友,也可以用删掉表格 (DROP TABLE,详细内容可以参考第 15 天)、改动表格 (ALTER TABLE) 等等方式来做修改。对於造成的不便,再次向大家道歉。

接着我们需要一个检查使用者资料是否存在的函式check_record

  • app/custom_models/CallDatabase.py
def check_record(user_id):
    conn, cursor = access_database()

    postgres_select_query = f"SELECT * FROM user_dualtone_settings WHERE user_id = '{user_id}';"

    cursor.execute(postgres_select_query)
    user_settings = cursor.fetchone()

    return user_settings

如果没有纪录,那就先初始化一笔暂时的纪录:

  • app/custom_models/CallDatabase.py
def init_record(user_id, message_id):
    conn, cursor = access_database()

    table_columns = '(user_id, message_id, mode, gradient_factor, first_tone, second_tone)'
    postgres_insert_query = f"INSERT INTO user_dualtone_settings {table_columns} VALUES (%s,%s,%s,%s,%s,%s)"

    record = (user_id, message_id, 'blend', '50', 'red', 'blue')

    cursor.execute(postgres_insert_query, record)
    conn.commit()

    cursor.close()
    conn.close()

    return record

以及更新纪录的方法:

  • app/custom_models/CallDatabase.py
def update_record(user_id, col, value):
    conn, cursor = access_database()

    postgres_update_query = f"UPDATE user_dualtone_settings SET {col} = %s WHERE user_id = %s"
    cursor.execute(postgres_update_query, (value, user_id))
    conn.commit()

    postgres_select_query = f"SELECT * FROM user_dualtone_settings WHERE user_id = '{user_id}';"

    cursor.execute(postgres_select_query)
    user_settings = cursor.fetchone()

    cursor.close()
    conn.close()

    return user_settings

全部写好之後,我们的档案架构看起来会像这样:

D:\appendix>tree /f
Folder PATH listing
Volume serial number is 9C33-6XXD
D:.
│   runtime.txt
│   requirements.txt
│   Procfile
│   Alma.py
│
└───app
    │   __init__.py
    │   models_for_line.py
    │   routes.py 
    │
    └───custom_models
            AlmaTalks.py
            CallDatabase.py

好了,那麽把完成的资料夹丢到 Heroku 上面吧!

棒!是不是真的记住了我们的设定呢。

https://ithelp.ithome.com.tw/upload/images/20201227/20120178IrM7kiKOuz.png
图十、记住所有设定的 LINE BOT

等等,说好今晚的双色打光呢?

抱歉,今晚已经有点晚了。这个我们留到下星期,讨论如何利用 Heroku 暂存空间的同时,再一起把所有东西补上。有兴趣的读者,也可以试着利用上面我们讨论出来的双色打光程序码,装备到 LINE BOT 上,看看 Heroku 是否跑得动这个双色打光的杰出操作 (当然是跑得动,不然这个系列文就?)。

好的,相信大家知道接下来又要进入最重要的工商时间了。是的,感谢 iT邦帮忙 和 博硕文化,LINE Bot by Python 全攻略 集结成书了,欢迎有兴趣的大家前往预购喔。


<<:  iOS APP 开发 OC 第五天, OC 数据类型

>>:  欸! 我觉得自动化测试的架构应该长这样,测试应该这样写。

[DAY 11]让BOT 24小时在线(2/3)

今天继续介绍如何在云端服务器上持续开启bot 但在进入replit之前需要在GitHub专案放进两个...

Day3 条件判断

今天来学学Vue里面的判别式v-if 跟v-show 1.v - if 在这里我们将条件设定为Sho...

Day6 Project1 - 履历

补充一点HTML的资讯,HTML从1995年至今已经发展了多个版本,目前主流使用为HTML5,每个版...

追求JS小姊姊系列 Day21 -- 工具人、姐妹不只身份的差别(上): 基本型别包裹器(wrapper object)

前情提要: 继续讲着工具力的源头 我:所以你们这些工具人,跟她的姐妹差别就是有没有能力? //pri...

[2021铁人赛 Day20] General Skills 17

今天我们稍微调动一下顺序,先解 General Skills 系列的最後一题, 因为跟昨天的题目算...