《赖田捕手:追加篇》第 34 天:妥善运用 LINE Notify 免费推播

第 34 天:妥善运用 LINE Notify 免费推播

「恩...我是在想你今天晚上要不要用push_message来发送讯息。」
「不行。」
「怎麽了?」
「我已经有其他计画了。」
「什麽计划?」
「我要去看个电影。」
「跟谁?」我的胃不禁一阵翻搅,拜托不要是-
「LINE Notify,」他说。

~节录自《聊天机器人的历史:泛滥的讯息》

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

《赖田捕手:追加篇》:

今天您将知道:

  1. LINE Notify 运作流程
  2. LINE Notify 登录服务
  3. LINE Notify 程序码实作

上一篇我们将使用者设定的条件当作双色打光的参数,完整的实作了一个可以客制化影像处理的 LINE BOT。我只能想得到两个形容词来形容这位 LINE BOT。第一个,是贴心。第二个,还是贴心。为什麽这样说呢?因为这位 LINE BOT 不仅对使用者输入的设定言听计从,更有甚者,我们甚至还让这位 LINE BOT 动用了push_message,为的就是能够给使用者更完整的讯息,并且创造更多与使用者的互动。
然而 LINE BOT 的push_message是有使用次数限制的。在免费方案 (也就是轻用量) 的 LINE BOT,每个月拥有 500 次push_message的额度,如图一。若要更多,就只能透过牺牲荷包,来召唤更多的push_message使用额度了。

https://ithelp.ithome.com.tw/upload/images/20210110/20120178w2fg0mN18D.png
图一push_message方案

不想这麽做的话,难道就没有其他方法了吗?

幸好我们还有 LINE Notify。

什麽是 LINE Notify 呢?这是一种特殊的 LINE 官方帐号,可以让使用者对某一个频道做连结 (订阅)。只要该频道有任何讯息或是动作,就可以透过 LINE Notify 发送通知给使用者。在我们接下来要讨论的内容里,所谓的某一种频道,当然就是 LINE BOT。
那麽重新整理一下思路再说一次。LINE Notify 是一种特殊的 LINE 官方帐号,让使用者可以对 LINE BOT 做连结 (订阅)。这麽一来,LINE BOT 就可以透过 LINE Notify 传送相关讯息给使用者。
咦?这样不是很怪吗?LINE BOT 要对使用者传讯息,还得透过 LINE Notify?
是的,没错,也就是让 LINE Notify 当作传话人的角色。虽然让 LINE BOT 利用 LINE Notify 与使用者来沟通,可能会造成不同的使用者体验,不过,这样做有一个最大的优势,那就是免费。不论是向使用者回覆讯息,向使用者推送讯息,都免费。这样一来,大家想把讯息推送的多完整、多即时、多恼人,都可以透过 LINE Notify 来达成愿望。
好啦,说了这麽多好话,还是得跟各位澄清一下 LINE Notify 的几个不足之处:

  1. 必须和使用者建立连结 (订阅)
  2. LINE Notify 只能发送文字、有限的 LINE 官方贴图、以及图片。而且每一次推送基本都必须发送一则文字讯息。

那麽,就开始来介绍 LINE Notify 的运作流程罗!

LINE Notify 运作流程

这边我试着以图二来描述 LINE Notify 与使用者以及 LINE BOT 之间的互动流程。

https://ithelp.ithome.com.tw/upload/images/20210110/20120178FLfm5hGb0D.png
图二、LINE USER ⇄ LINE Notify ⇄ LINE BOT

首先是第一阶段,也就是产生授权网址 (Auth Link)。为了要有授权网址,我们要先在 LINE Notify 上面登录一个服务,拿到 Client ID、Redirect URI 这两个资料,而 State 是我们可以自订的资料。根据 Client ID、Redirect URI 和 State,我们可以产生一个相对应的授权连结,透过 LINE BOT 发送给使用者。
到了第二阶段,使用者看到了我们的授权网址,点击下去同意建立连结 (订阅) 之後,会产生一组 Code,并传送到 Redirect URI 这个地方。方便起见,我们当然是将 Redirect URI 指向 LINE BOT,让 LINE BOT 直接接收这一组 Code。
第三阶段,拿到代表使用者同意建立连结的 Code 之後,LINE BOT 就可以向 LINE Notify 申请 Access Token。这一个步骤,LINE BOT 需要拿出包括 Client ID、Client Secret、Redirect URI、和 Code 等资料。确认无误之後,LINE Notify 就会传回 Access Token。
第四阶段,有了 Access Token 的 LINE BOT,就可以利用这一组权杖当作媒介,透过 LINE Notify 发送讯息给特定的使用者了。

LINE Notify 登录服务

大致的流程就如上所述。为了要让整个流程得以展开,首先我们得在 LINE Notify 上面登录一个服务。先来到 LINE Notify 的官方网站,右上角以 LINE 帐号登入。登入之後,选择管理登录服务,并点选登录服务。如此会来到一个建立新服务的画面,如图三

https://ithelp.ithome.com.tw/upload/images/20210111/20120178CUIIvazesv.png
图三、登录服务

里面最重要的有三个栏位:服务名称、电子邮件信箱、以及 Callback URL。因为所有和 LINE Notify 连动的服务,都是透过 LINE Notify 发送讯息给使用者。那麽使用者要怎麽知道这次送来的讯息是哪一个服务呢?靠的就是这个服务名称。所以服务名称请写的简单明了,让使用者一看就知道这是代表我们的 LINE BOT 送来的讯息。电子邮件信箱则是启用 LINE Notify 服务时,需要点击连结进行认证用的,选一个你收得到信的电子邮件信箱就可以。最後一个 Callback URL 想当然尔就是前面提过的 Redirect URI。以我目前想要为双色打光 LINE BOT 搭配的 LINE Notify 服务而言,我所填写的栏位如图四所示。注意这边我所填写的 Redirect URI 是https://你-APP-的名字.herokuapp.com/callback/notify,这是我们之後要准备接收 LINE Notify 相关资料的路由,待会必须要实作出来。

https://ithelp.ithome.com.tw/upload/images/20210111/20120178zPoLf4tTf5.png
图四、双色打光 feat. LINE Notify

确认无误之後,按下登录并完成电子邮件认证,这样一来新的 LINE Notify 服务就成功建立了。此时我们查看这个 LINE Notify 服务,应该可以看到 Client ID 和 Client Secret 这两个资料,如图五

https://ithelp.ithome.com.tw/upload/images/20210111/20120178TgDbF4d23p.png
图五、代表我们 LINE Notify 服务的 Client ID 和 Client Secret

这样就完成登录服务的部分了!

LINE Notify 程序码实作

注:第二阶段的详细实作已经於 2021.01.11 更新

接下来进入大家最感兴趣的部份了,也就是该怎麽把图二的所有流程用程序码实作出来。为了方便阅读,我们把图二再看一次,如图六

https://ithelp.ithome.com.tw/upload/images/20210111/20120178FptkFAigI9.png
图六、LINE USER ⇄ LINE Notify ⇄ LINE BOT,again

我们分阶段来演示。首先是第一阶段,产生授权网址:

import os, urllib

client_id = os.environ['NOTIFY_CLIENT_ID']
client_secret = os.environ['NOTIFY_CLIENT_SECRET']
redirect_uri = f"https://{os.environ['YOUR_HEROKU_APP_NAME']}.herokuapp.com/callback/notify"

def create_auth_link(user_id, client_id=client_id, redirect_uri=redirect_uri):
    
    data = {
        'response_type': 'code', 
        'client_id': client_id, 
        'redirect_uri': redirect_uri, 
        'scope': 'notify', 
        'state': user_id
    }
    query_str = urllib.parse.urlencode(data)
    
    return f'https://notify-bot.line.me/oauth/authorize?{query_str}'
  • 第二行:client_id = os.environ['NOTIFY_CLIENT_ID']
    因为这些程序码最後都会成为我们 LINE BOT 的一部分,放到 Heroku 上面,所以可以把client_idclient_secret这些资料利用环境变数的方式存到 Heroku 里面。

  • 第四行:redirect_uri = f"https://{os.environ['YOUR_HEROKU_APP_NAME']}.herokuapp.com/callback/notify"
    这是我们之後要准备接收 LINE Notify 相关资料的路由,待会必须要实作出来。

  • 第六行→第十二行:data = {
    前面说过,要产生授权网址,我们需要准备好 Client ID、Redirect URI、以及 State。在这里面,State 是可以任意输入的资料,也是让我们可以藉由 LINE Notify 推送讯息到指定使用者最重要的手段。怎麽说呢?因为最後我们将会拿到的 Access Token 其实没有任何跟使用者相关的资料,单看 Access Token 是没办法知道 LINE Notify 会把讯息传给谁的,所以一开始我们就要记好。可是要怎麽记住呢?这个任意输入的 State 就是一个非常方便的变数。我们可以将 LINE 的使用者代码 (user_id) 放到 State 里面,如此一来,这一个授权网址就代表一个使用者,而根据该授权网址所产生的 Access Token,就可以连结到这个特定的使用者身上了。

  • 第十四行:return f'https://notify-bot.line.me/oauth/authorize?{query_str}'
    根据给定的资料产生授权网址,如此完成第一阶段。

第二阶段从使用者进入我们给予的网址开始,到 LINE BOT 拿到 Code 和 State 为止。使用者点击授权网址之後,应该会带到如图七所示的网页上:

https://ithelp.ithome.com.tw/upload/images/20210111/20120178q3AuZ9h3dm.png
图七、授权网址

一般来说会选择连动到**「透过 1 对 1 聊天接收 LINE Notify 通知」**。当使用者选择好接收通知的聊天室,同意并连动之後, LINE Notify 会将 Code 和 State 等资料送到 Redirect URI。为了方便起见,我们希望这些资料可以被送到 LINE BOT 手上,因此把 Redirect URI 设定成https://你-APP-的名字.herokuapp.com/callback/notify
现在,让我们试着用flask来实作这一个路由,并且藉由解析查询字串 (query string) 来拿到使用者同意并连动之後产生的 Code 跟其实是使用者代码 (user_id) 的 State。

  • app/routes.py
from flask import request

@app.route("/callback/notify", methods=['GET'])
def callback_nofity():
    assert request.headers['referer'] == 'https://notify-bot.line.me/'
    code = request.args.get('code')
    state = request.args.get('state')

    # 接下来要继续实作的函式
    access_token = get_token(code, client_id, client_secret, redirect_uri)

    return '恭喜完成 LINE Notify 连动!请关闭此视窗。'
  • 第二行:@app.route("/callback/notify", methods=['GET'])
    这就是我们要建立的路由,https://你-APP-的名字.herokuapp.com/callback/notify

  • 第四行:assert request.headers['referer'] == 'https://notify-bot.line.me/'
    先确定一下这是从 LINE Notify 发送过来的资料。

  • 第五行→第六行:code = request.args.get('code')
    LINE Notify 是用查询字串的方式将资料送过来的。所以如果大家有机会看到 LINE Notify 向我们 Redirect URI 请求的网址的话,看起来应该会像这样:https://你-APP-的名字.herokuapp.com/callback/notify?code=一串Code&state=一串State。要拿到查询字串里面的资料也不困难,用flask所提供的方法request.args.get就可以了。

  • 第八行:access_token = get_token(code, client_id, client_secret, redirect_uri)
    有了 Code 之後,就可以到第三阶段去向 LINE Notify 请求 Access Token 了。这是等等要来实作的函式。

  • 第九行:return '恭喜完成 LINE Notify 连动!请关闭此视窗。'
    这个则是使用者同意并连动之後,最後会看到的内容。所以才叫 Redirect URI,也就是使用者同意并连动之後,会被重新导向的网址。当然大家可以把这个网页做得漂亮一点,不过我们这边简单起见,就用一行字恭喜使用者完成订阅,如图八

https://ithelp.ithome.com.tw/upload/images/20210111/20120178p8Hh0ig4UE.png
图八、恭喜您完成连动!

第三阶段,利用使用者同意并连动之後产生的 Code,来向 LINE Notify 拿取最重要的 Access Token:

import json

def get_token(code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri):
    url = 'https://notify-bot.line.me/oauth/token'
    headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': redirect_uri,
        'client_id': client_id,
        'client_secret': client_secret
    }
    data = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(url, data=data, headers=headers)
    page = urllib.request.urlopen(req).read()
    
    res = json.loads(page.decode('utf-8'))
    return res['access_token']
  • 第十二行:data = urllib.parse.urlencode(data).encode()
    准备好需要的资料,包括client_idclient_secretredirect_uricode

  • 第十四行:page = urllib.request.urlopen(req).read()
    传送资料到 LINE Notify,并读取 LINE Notify 回传的资料。

  • 第十六行:return res['access_token']
    回传的资料可以利用json.loads转换为字典物件 (dict)。该物件当中有一个键 (key) 为access_token的资料,就示我们朝思暮想的 Access Token。

拿到 Access Token 的时候,我们可以利用 Heroku 提供的资料库把 Access Token 和对应的使用者代码 (user_id) 给储存起来。这边我就不再详述该如何实作,大家可以参考前面的内容 (第 17 天第 32 天) 自由发挥。完成之後,我们就可以进入第四阶段,开始肆无忌惮的骚扰使用者了:

def send_message(access_token, text_message, picurl):

    url = 'https://notify-api.line.me/api/notify'
    headers = {"Authorization": "Bearer "+ access_token}

    data = {'message': text_message, 
            "stickerPackageId": 2, 'stickerId': 38, 
            'imageThumbnail':picurl, 'imageFullsize':picurl}

    data = urllib.parse.urlencode(data).encode()
    req = urllib.request.Request(url, data=data, headers=headers)
    page = urllib.request.urlopen(req).read()
  • 第四行:data = {'message': text_message,
    代表要向使用者发送的文字讯息text_message。在 LINE Notify 的规则里,每次要发送讯息,都必须带有一则文字讯息,所以这个栏位是必须的。

  • 第五行:"stickerPackageId": 2, 'stickerId': 38,
    发送贴图的方法。上面这行程序码代表发送第 2 组官方贴图组当中的第 38 个贴图。

  • 第六行:'imageThumbnail':picurl, 'imageFullsize':picurl}
    发送图片的方法。一样必须要用网址的方式传送图片资料。

今天实在是非常的匆忙,如果有任何介绍得不够清楚的,都欢迎大家在这边留言讨论。有兴趣的读者们相信已经可以开始磨刀霍霍准备要动手实作一个结合 LINE Notify 的 LINE BOT 了。我个人详细的内容则会在下一周仔细介绍。

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


<<:  你要的是Entity Framework吗?

>>:  推论 & 聚合( Inference and Aggregation)

Day28 客制化Hook

昨天了解了Hook的概观之後,今天要利用Hook的规则打造一个客制化的Hook。 以下用React官...

[Day 21] Edge Impulse + BLE Sense实现唤醒词辨识(中)

=== 书接上回 [Day 20] Edge Impulse + BLE Sense实现唤醒词辨识(...

Day 07 借箸代筹(1):运算式、运算子

前文曾述及的陈述句(statement),是构成JavaScript程序的两大类语法之一,其之二,即...

JavaScript入门 Day07_如何使用字串2

今天呢,来讲讲其他有关字串的用法~ 若是我们今天想知道这个字串的第几个字是什麽 那摸 我们就可以使用...

Day52. 范例:新产品开发(职责链模式)

本文同步更新於blog 情境:公司开发了一个新产品,客户端有许多不同的请求 客户端的请求类别 &...