LoginSignup
3
3

【WEB】LINEから他サービスAPIを使う(OpenAI/wiki/twitter/youtube)

Last updated at Posted at 2023-04-26

自動応答Linebotを作ったメモ。流行りのOpenAIも使ってみた。
(機能追加次第追記・修正していく予定です)

サーバ:Raspberry Pi4 ModelB + Ubuntu 22.04.2
プログラム:python,flask
外部連携:ngrok,webhook

構成はこんな感じで、
image.png

デフォルトではOpenAIで応答し、LINEメッセージの先頭に"twitter XXX" や "wiki XXX"を入れることでそれぞれのサービスからツイートや記事を検索して返してくれます。
「調べモノbot」みたいなもんですね。
※2023/8,"twitter"は"X"になり、searchAPIの使用が有料となってしまいました...

AIの返答で他サービスとの連携はできないので機能を追加するのはいいのですが、wikiは若干役割が被ります。が!wikipediaって記事内の関連用語からサーフィンするの楽しいですよね?アレはチャットではできないからいいんです!(強引)

①LINEBOT作成

メッセージを送る為のチャットボットを用意します。AI応答の設定に合わせてキャラクターを作ってもいいかも。アカウント作成方法は割愛。

LINE Developersの設定画面から
払い出されたアクセストークンとチャネルシークレットを確認しておきます。

チャネルアクセストークン

LINEDevelopers⇒MessagingAPI設定⇒チャネルアクセストークン
image.png

チャネルシークレット

LINEDevelopers⇒チャネル基本設定⇒チャネルシークレット
image.png
webhookの設定は手順の最後に行います。

②TwitterAPI登録

TwitterのAPIを使用するためにデベロッパー登録が必要です。

キーは払い出し後に確認ができなくなるので注意。
image.png

③OpenAI登録

OpenAIはとてもシンプルです。キー1つだけ。

現状OpenAIは従量課金(初回トライアル3ヶ月は$18分までは無料)のため、ここで課金制限をかけられます。
image.png

④YoutubeAPI登録

Youtube Search APIを使用。こちらもかなりシンプルです。

⑤必要なパッケージのインストール

プログラムを動かすサーバ(ここではローカルのUbuntu)で諸々必要なパッケージをインストールしておきます。
以下コマンド実行

apt-get install python-pip   ##pythonパッケージ
pip install line-bot-sdk     ##LINEBOT
pip install flask            ##Flask
pip install openai           ##OpenAIのAPI
pip install tweepy           ##TwitterAPIライブラリ
pip install wikipedia        ##WikipediaAPIライブラリ
pip install google-api-python-client  ##GoogleAPIライブラリ

※本記事執筆時(2023/4)のバージョン

line-bot-sdk           2.3.0
Flask                  2.2.2
openai                 0.27.2
tweepy                 4.13.0
wikipedia              1.4.0
google-api-python-client  2.102.0

pip list コマンドで確認できる。

⑥環境変数を書いておく

コードの中にAPIキーベタ書きはNGなので、OSの環境変数として定義しておきましょう。

root@host01:~# cat ~/.bash_profile
export YOUR_CHANNEL_ACCESS_TOKEN=XXXXXX    ##①で確認
export YOUR_CHANNEL_SECRET=XXXXXX          ##①で確認
export TWITTER_API_KEY=XXXXXX              ##②で確認
export TWITTER_API_SECRET_KEY=XXXXXX       ##②で確認
export TWITTER_ACCESS_TOKEN=XXXXXX         ##②で確認
export TWITTER_ACCESS_TOKEN_SECRET=XXXXXX  ##②で確認
export OPENAI_ACCESS_TOKEN=XXXXXX          ##③で確認
export YOUTUBE_API_KEY=XXXXXX              ##④で確認
export FLASK_APP=/opt/line-bot/main.py     ##応答プログラム置き場
root@host01:~#

⑦応答プログラムのコード実装

メインとなる応答プログラムのコードです。
twitter,wikiの文字列で引っ掛けてそれぞれのAPIを実行し、それ以外はすべてAIから応答させるようにします。

main.py
# FlaskとLinebotライブラリのインポート
from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, ImageSendMessage,
)

# OS,正規表現,Twitter,wikipedia,youtube,OpenAIライブラリのインポート
import os
import re
import tweepy
import wikipedia
import openai

from apiclient.discovery import build

app = Flask(__name__)

## APIキーの定義。OSの環境変数に入れておく
## LINEBOTとwebhook用キー定義
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)

## Twitter用キー定義
TWITTER_API_KEY = os.environ["TWITTER_API_KEY"]
TWITTER_API_SECRET_KEY = os.environ["TWITTER_API_SECRET_KEY"]
TWITTER_ACCESS_TOKEN = os.environ["TWITTER_ACCESS_TOKEN"]
TWITTER_ACCESS_TOKEN_SECRET = os.environ["TWITTER_ACCESS_TOKEN_SECRET"]
auth = tweepy.OAuthHandler(TWITTER_API_KEY, TWITTER_API_SECRET_KEY)
auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET)
api = tweepy.API(auth)

## Wiki検索時の言語セット
wikipedia.set_lang("ja")

## Youtube用キー定義
YOUTUBE_API_KEY = os.environ["YOUTUBE_API_KEY"]
youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY)

## OpenAI用キー定義
openai.api_key = os.environ["OPENAI_ACCESS_TOKEN"]

## OpenAI回答用変数定義 #過去の会話を理解させる場合に使用
#assist1 = ""


## Linebotに送られたメッセージ処理
@app.route("/", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)
    return 'OK'


## メッセージが "wikipedia XXX"または "wiki XXX"の場合、wikipedia記事の検索を実行する
## メッセージが "twitter XXX"または "ツイート XXX"の場合、tweet検索を実行して5件表示する
## メッセージが "youtube XXX または" "ユーチューブ XXX"の場合、動画検索を実行して5件表示する
## 上記以外の場合はChatGPTから応答する
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    send_message = event.message.text
    match = re.search("(wiki|wikipedia) (.+)", send_message.lower())
    match1 = re.search("(twitter|ツイート) (.+)", send_message.lower())
    match2 = re.search("(youtube|ユーチューブ) (.+)", send_message.lower())
    if match:
        query = match.group(2)
        try:
            wikipedia_page = wikipedia.page(query)
            # wikipedia.page()の処理で、ページ情報が取得できれば、以下のようにタイトル、リンク、サマリーが取得できる。
            wikipedia_title = wikipedia_page.title
            wikipedia_url = wikipedia_page.url
            wikipedia_summary = wikipedia.summary(query)
            reply_message = '' + wikipedia_title + '\n' + wikipedia_summary + '\n\n' + '【詳しくはこちら】\n' + wikipedia_url
        # ページが見つからなかった場合
        except wikipedia.exceptions.PageError:
            reply_message = '' + query + '\nについての情報は見つかりませんでした。'
        # 曖昧さ回避に引っかかった場合
        except wikipedia.exceptions.DisambiguationError as e:
            disambiguation_list = e.options
            reply_message = '以下の候補から近いものを再入力してください。\n\n'
            for word in disambiguation_list:
                reply_message += '' + word + '\n'
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(reply_message)
        )
    elif match1:
        query = match1.group(2)
        tweets = api.search_tweets(q=query, count=5, result_type="mixed", include_entities=True)
        reply_message = ""
        for tweet in tweets:
            reply_message += "=====================\n" + tweet.user.name + "\n@" + tweet.user.screen_name + "\n" + tweet.created_at.strftime('%Y/%m/%d %H:%M:%S') + "\n=====================\n" + tweet.text + "\n" + "https://twitter.com/" + tweet.user.screen_name + "/status/" + tweet.id_str + "\n\n\n\n"
        if reply_message == "":
            reply_message = "該当するツイートは見つかりませんでした。"
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=reply_message)
        )
    elif match2:
        query = match2.group(2)
        search_response = youtube.search().list(
            q=query,
            type='video',
            part='id,snippet',
            maxResults=5  # 検索結果の最大数を指定
        ).execute()
        reply_message = ""
        for search_result in search_response.get('items', []):
            video_title = search_result['snippet']['title']
            video_url = "https://www.youtube.com/watch?v=" + search_result['id']['videoId']
            reply_message += '' + video_title + '\n' + video_url + '\n\n'
        if reply_message == "":
            reply_message = "該当する動画は見つかりませんでした。"

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

    else:
#     global assist1  # assist1をグローバル変数にする。#過去の会話を理解させる場合に使用
     response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
#            {"role": "system", "content": "任意の設定を書く。例:占い師になって下さい。"},  # AIに人格を与える場合に使用
#            {"role": "assistant", "content": assist1}, # 過去の会話を理解させる場合に使用
            {"role": "user", "content": send_message},
        ]
     )
#     assist1 = response["choices"][0]["message"]["content"]  # assist1を更新 過去の会話を理解させる場合に使用
     line_bot_api.reply_message(
         event.reply_token,
         TextSendMessage(text=response["choices"][0]["message"]["content"]),
     )

if __name__ == '__main__':
    app.run()

assist1を有効にすることでAI応答の回答を記憶させ"会話"ができるようにもできますが、
使用トークン数が増大するのと、複数人で使うことを考えると一問一答形式の方がいいと思います。

また、ここでは指定していませんが"system", "content":のセクションで人格を与えることができるようです。しかも結構細かいめに↓

作成したLINEBOTのキャラクターに合わせた設定にすると面白いかもしれません。オラ悟空みたいなしゃべり方にしてぇぞ!!

⑧ngrokの用意

元々"heroku"というPaaSのサービスを利用するつもりでしたが、有料化してしまったのとラズパイが手に入ったのでローカルで"ngrok"を使うことにしました。
簡単なWebサーバーならサクッと立ち上げることが出来る優れモノですが、ローカルサーバが外部に公開されることになるので注意が必要です。

ダウンロード、コマンド配置

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm.zip
unzip ngrok-stable-linux-arm.zip
mv ngrok /usr/local/bin/

バージョン確認

root@host01:~$ ngrok version
ngrok version 2.3.40
root@host01:~$

ngrokアカウント認証

ngrokを実行すると公開用のURLが払い出されますが、デフォルトだと2時間で無効になります。
永久に有効にするには以下からアカウント登録し、Authキーを使うことでプロセスが落ちるまではURLが変わりません。
※アカウントを登録したとしても、Freeプランだとngrokプロセスが落ちると再度URLが新規に払い出されます。

authキー取得

image.png

認証の設定

ngrok authtoken {取得したAuthToken}

ngrok.ymlという設定ファイルに、コマンドラインのオプションや通信の設定をtunnelsセクションに指定することで実行時のパラメータを省くことができます。

root@host01:~# cat .ngrok2/ngrok.yml
authtoken: {取得したAuthToken}
root@host01:~#

⑨プログラムをバックグラウンド実行/URL公開

flaskコマンドでプログラムを実行し、ngrokコマンドで外部公開。
バックグラウンドでサーバからログアウトしてもプロセスを実行し続けるようにします。
flaskとngrokのポートは合わせるようにしましょう。

root@host01:~#
root@host01:~# nohup flask run --reload --port 8080 &
[1] 1832
root@host01:~# nohup ngrok http 8080 &
[2] 1835
root@host01:~# curl http://localhost:4040/api/tunnels | grep ngrok
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1028  100  1028    0     0   277k      0 --:--:-- --:--:-- --:--:--  334k
{"tunnels":[{"name":"command_line","uri":"/api/tunnels/command_line","public_url":"https://xxxx.ngrok-free.app","proto":"https","config":{"addr":"http://localhost:8080","inspect":true},"metrics":{"conns":{"count":249,"gauge":0,"rate1":1.(略)
root@host01:~#

ngrokで公開されたURLはcurlでトンネル確認して行います。
"public_url"の値がwebhookのURLとなり、これをLINE Developersのwebhookの設定に入れます。
ちなみに止めたい時はプロセスをkillすればOKです。

⑩Webhook連携

公開したURLをLINE Developersでwebhook設定します。
image.png
"検証"をクリックすると疎通テストができます。"成功"と出ればOK.

完成!

これで完成です。さっそくLINEメッセージを送ってみましょう!

wikipedia

IMG_8322.PNG

Twitter

IMG_8323.PNG

Youtube

IMG_0378.PNG

OpenAI

IMG_8324.PNG

当然自宅ラズパイなのでこいつが落ちると動かなくなります(既読スルーされる)。bot自体はまだまだ色々できそうなのでやってみたいですね。

以上ぅ

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3