Djangoで、Line Botを使いつつ1対1のチャット機能

2019-07-30 / PythonDjangoLine

概要

Line公式アカウントには、Botモードチャットモードの2つのモードがあります。チャットモードは普通のラインで、個別にチャットをすることができます。Botモードはユーザーが送信した内容に応じて自動応答するのによく使われますね。

Botモードは、Webhookという機能によって成り立っています。これは何かというと、ユーザーがメッセージを送信したり、友達追加をしたり、ブロックしたりそれを解除したり、そういった何らかのイベントの際に、好きな処理をフックする仕組みのことです。具体的に言うと、メッセージの送信や友達追加といった操作をされたときに、私達が作ったDjangoのビューを呼び出してもらえます。この際メッセージ内容やLineユーザーIDといった情報も渡されるので、これを元に応答内容を作成して返信する一般的なBotの運用や、渡されたLineユーザーIDをデータベースに保存しておき、好きなときにプログラム上からメッセージを送信する、といったことも行うことができます。

しかし、Botモードとチャットモードは両立できません。チャットモードにすると、Webhook機能が使えなくなるのです。そこで今回は、Botモードを使いつつ1対1のチャット機能をDjango側の機能を使って実装していきます。

まずですが、ユーザーがWebサイトのQR画像を読み込むと、Django側のデータベースにLineユーザーIDが登録されるようにしておきましょう。Django、Lineで更新を通知で解説しているので、参考にしてください。また、今回の記事の完全なソースコードは、Githubにあります。

こんな感じで、友達になったLineユーザーの一覧が表示され...

Djangoサイト上で、そのユーザーとのメッセージ履歴や送信が行なえます。

相手側のLineにも、ちゃんと表示されますね。

自動応答機能をなくす

Line公式アカウントのサイトで、次のように設定しておきます。

応答モードがBot、Webhookがオンになっていることを確認してください。そして、応答メッセージをオフにしてください。これをしないと、ユーザーがメッセージを送信した際に、Djangoのビューが呼ばれなくなり、公式アカウントサイト内で設定した自動メッセージが返されるだけになります。

モデルの修正

models.pyを次のようにしておきます。

class LinePush(models.Model):
    """Lineでのプッシュ先を表す"""
    user_id = models.CharField('ユーザーID', max_length=100, unique=True)
    display_name = models.CharField('表示名', max_length=255)

    def __str__(self):
        return self.display_name


class LineMessage(models.Model):
    """Lineの各メッセージを表現する"""
    push = models.ForeignKey(LinePush, verbose_name='プッシュ先', on_delete=models.SET_NULL, blank=True, null=True)
    text = models.TextField('テキスト')
    is_admin = models.BooleanField('このメッセージは管理者側の発言か', default=True)
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        return f'{self.push} - {self.text[:10]} - {self.is_admin}'

LinePushモデルは、Django、Lineで更新を通知で作ったモデルと殆ど同じです。QR画像を読み込んで友達になってくれた、各ユーザーのLineIDが詰まっています。今回はそれにちょい足しで、ラインの表示名も持たせるようにしました。場合によっては、このモデルの代わりにDjango組み込みのUserや、それにOneToOneで紐づくモデルで、そいつがLineIDや表示名を保持することになるでしょう。

LineMessageは、Lineの1つ1つのメッセージを表現するモデルです。pushフィールドはそのメッセージが誰かを表し、textはメッセージの内容、is_adminはロジック上あると便利なのでつけています。これについては、後で実感するでしょう。

Web hook ビューの修正

callbakビューを次のように変更します。

import linebot
line_bot_api = linebot.LineBotApi('発行したアクセストークン')
...
...
@csrf_exempt
def callback(request):
    """ラインの友達追加時・メッセージ受信時に呼ばれる"""
    if request.method == 'POST':
        request_json = json.loads(request.body.decode('utf-8'))
        events = request_json['events']
        line_user_id = events[0]['source']['userId']

        # チャネル設定のWeb hook接続確認時にはここ。このIDで見に来る。
        if line_user_id == 'Udeadbeefdeadbeefdeadbeefdeadbeef':
            pass

        # 友達追加時
        elif events[0]['type'] == 'follow':
            profile = line_bot_api.get_profile(line_user_id)
            LinePush.objects.create(user_id=line_user_id, display_name=profile.display_name)

        # アカウントがブロックされたとき
        elif events[0]['type'] == 'unfollow':
            LinePush.objects.filter(user_id=line_user_id).delete()

        # メッセージ受信時
        elif events[0]['type'] == 'message':
            text = request_json['events'][0]['message']['text']
            line_push = get_object_or_404(LinePush, user_id=line_user_id)
            LineMessage.objects.create(push=line_push, text=text, is_admin=False)

    return HttpResponse()

Django、Lineで更新を通知から少し変更があります。一つは、ラインの表示名をもたせるために、profile = line_bot_api.get_profile(line_user_id)の部分でLineユーザーの情報を取得し、LinePush作成時に利用しています。もう一つがメッセージ受信時の処理が増えたことで、Django側に履歴として残すために、LineMessageを作成しています。

Lineユーザー一覧ページの作成

まず、次のようなビューを作成します。

class LineUserList(generic.ListView):
    model = LinePush
    template_name = 'app/line_user_list.html'

そして、line_user_list.htmlを作ります。やっていることは、LinePushの一覧を表示するだけですね。

{% extends 'app/base.html' %}


{% block content %}
    <h1>Lineユーザーリスト</h1>
    {% for line_user in linepush_list %}
        <p><a href="{% url 'app:line_message_list' line_user.pk %}">{{ line_user }}</p>
    {% endfor %}
{% endblock %}

メッセージのやり取りページ

上の一覧ページから、メッセージの表示と送信ができるページを作ります。まず、次のようなビューを作ります。

class LineMessageList(generic.CreateView):
    model = LineMessage
    fields = ('text',)
    template_name = 'app/line_message_list.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        line_push = get_object_or_404(LinePush, pk=self.kwargs['pk'])
        context['message_list'] = LineMessage.objects.filter(push=line_push)
        context['push'] = line_push
        return context

    def form_valid(self, form):
        line_push = get_object_or_404(LinePush, pk=self.kwargs['pk'])
        message = form.save(commit=False)
        message.push = line_push
        message.is_admin = True
        message.save()
        line_bot_api.push_message(line_push.user_id, messages=TextSendMessage(text=message.text))
        return redirect('app:line_message_list', pk=line_push.pk)

このビューは各ユーザーへのメッセージの配信と、やり取りしているメッセージの一覧表示を行います。

そして、line_message_list.htmlを作ります。そのユーザーのメッセージを左、自分のメッセージを右に表示するなどは、CSSで設定してください。

{% extends 'app/base.html' %}


{% block content %}
    <h1>{{ push.display_name }} チャット</h1>
    {% for message in message_list %}
        {% if message.is_admin %}
            <div class="card right">
        {% else %}
            <div class="card left">
        {% endif %}
    {{ message.text }} - {{ message.created_at }}
    </div>
    {% endfor %}

    <form action="" method="POST">
        {{ form.as_p }}
        <button type="submit">送信</button>
        {% csrf_token %}
    </form>
{% endblock %}

この記事の関連記事

Django、Lineで更新を通知

2019-07-27 / PythonDjangoLine

- Djangoで、Webサイトの更新情報を通知するシリーズの一つです。Lineで、Webサイトの更新を伝えていきます。

コメント欄

記事にコメントする

まだコメントはありません。