わがままゴルファーのためのLINE Bot

## 1. ゴルフの要素って多くない！？
こんなどうでもいいことを友達に聞くのもアレなのでChatGPTに同意を求めてみた。
![スクリーンショット 2023-11-20 224428.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3554996/563b2c74-80c3-e4e2-d54b-2cd3c903af87.png)
プレイするという観点からもゴルフは考えることが多くて複雑であると
改めて教えてもらった。

私が個人的にゴルフをしていく上で気になるのは
* **どうやって練習したらいいのか**
* **ラウンドに必要なものは揃っているか**
* **予約はどうやって取ればいいか**
* **ラウンドの日の天気はいいのか**
などである。

もちろん時間をかければわかる。

練習の情報もあふれているが、取捨選択の時間があれば
必要で効率のよい練習方法にたどり着けるだろう。

ラウンド前に必要なものが足りていなければ
買いに行けばいい。

ゴルフ場に電話したり
アプリを使えば予約もできる。

ラウンド当日の天気も
webで予報を見ればわかる。

**しかし！**

やること多すぎないか。
忘れてしまうことや
抜け落ちてしまうことも
ありそうじゃないか。

**誰かまとめてくれー！**

そうだ。LINE Botでまとめてしまおう。

## 2. ゴルファー（自分）のためのLINE Botが完成した

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ゴルフのことに特化させた自分専用のLINEBot<a href="https://twitter.com/hashtag/%E3%82%B4%E3%83%AB%E3%83%95?src=hash&amp;ref_src=twsrc%5Etfw">#ゴルフ</a> <a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング</a> <a href="https://twitter.com/hashtag/%E5%8B%95%E7%94%BB%E7%B7%A8%E9%9B%86?src=hash&amp;ref_src=twsrc%5Etfw">#動画編集</a> <a href="https://t.co/NN72FGGvQw">pic.twitter.com/NN72FGGvQw</a></p>&mdash; Yokki (@K822Dr) <a href="https://twitter.com/K822Dr/status/1726277140696719662?ref_src=twsrc%5Etfw">November 19, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


<details><summary> PushMessageでテキストを送るコード</summary>

```js
'use strict';

const line = require('@line/bot-sdk');
// const dotenv = require('dotenv');　// dotenv使うときにコメントアウト外してください
// dotenv.config();

// Messaging APIを利用するための鍵を設定します。
const config = {
  channelSecret: process.env.CHANNEL_SECRET || '**********',
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '***********',
};
const client = new line.messagingApi.MessagingApiClient(config)

const main = async () => {

    const messages = [{
        type: 'text',
        text: 'ナイスショット！\nいい調子だね！'
    }];

    try {
        const res = await client.broadcast({messages});
        console.log(res);        
    } catch (error) {
        console.log(`エラー: ${error.statusMessage}`);
        console.log(error.originalError.response.data);
    }
}

main();
```
</details>

<details><summary> PushMessageでスタンプを送るコード</summary>

```js
'use strict';

const line = require('@line/bot-sdk');
// const dotenv = require('dotenv');　// dotenv使うときにコメントアウト外してください
// dotenv.config();

// Messaging APIを利用するための鍵を設定します。
const config = {
  channelSecret: process.env.CHANNEL_SECRET || '********',
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '*********',
};
const client = new line.messagingApi.MessagingApiClient(config)

const main = async () => {

    const messages = [{
        "type": "sticker",
        "packageId": "1070",
        "stickerId": "17849"
      }];

    try {
        const res = await client.broadcast({messages});
        console.log(res);        
    } catch (error) {
        console.log(`エラー: ${error.statusMessage}`);
        console.log(error.originalError.response.data);
    }
}

main();
```
</details>

<details><summary> 自動的にゴルフ関連の返答を返すコード</summary>

```js
'use strict';

// ########################################
//               初期設定など
// ########################################

// モジュールの読み込み
const line = require('@line/bot-sdk');
const OpenAI = require('openai');
const express = require('express');
// const dotenv = require('dotenv'); //dotenv使うときはコメントアウト外してください
// dotenv.config();

// 設定
const config = {
  channelSecret: process.env.CHANNEL_SECRET || '**********',
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN || '**********',
};

// GPTのAPIKEY
const apiKey = process.env.OPENAI_API_KEY || '**********';
const openai = new OpenAI({apiKey});

const makeCompletion = async (userMessage) => {
  const prompt = {
      role: "system", 
      content: "ゴルフに関連させて答えてください" // プロンプトを入力
  };

  const sendMessage = [prompt, userMessage]
  console.log(sendMessage);
  return await openai.chat.completions.create({
    messages: sendMessage,
    model: "gpt-3.5-turbo-1106",
  });
};

const client = new line.messagingApi.MessagingApiClient(config);
// メッセージイベントの処理
async function handleEvent(event) {
  // テキストメッセージ以外は無視
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  const userMessage = {
    role: "user",
    content: event.message.text
  };

  // ChatGPT APIにリクエストを送る
  try {
    const completion = await makeCompletion(userMessage);
    // レスポンスから返答を取得
    console.log(completion.choices[0].message.content); // レスポンスをみたいときにコメントアウトを外してください
    const reply = completion.choices[0].message.content;
    // 返答をLINEに送る
    return client.replyMessage({
      replyToken: event.replyToken, 
      messages:[{
        type: 'text',
        text: reply
      }]
    });
  } catch (error) {
    // エラーが発生した場合はログに出力
    console.error(error);
    return Promise.resolve(null);
  }
}
//以下サーバーを使うためのコード
const port = process.env.PORT || 3000;
const app = express()
  .get('/', (_, res) => res.send('hello LINE Bot'))
  .post('/webhook', line.middleware(config), (req, res) => {
    if (req.body.events.length === 0) {
      res.send('Hello LINE BOT! (HTTP POST)');
      console.log('検証イベントを受信しました');
      return
    } else {
      console.log(req.body.events);
    }
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
  })
app.listen(port);
console.log(`PORT${port}番でサーバーを実行中です`)
```
</details>

<details><summary> リッチメニューの実装</summary>

![231121ラインスクショ3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3554996/1eb8f8fd-7fae-2c92-407d-7024ef7032a4.jpeg)

</details>

<details><summary> LINE Botとの会話</summary>

![231121ラインスクショ1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3554996/c5afefc9-5fa1-0dd8-4d06-2d37b073d7c8.jpeg)

![231121ラインスクショ2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3554996/1f9ef4c9-858c-3925-96a7-4246e02d0d5e.jpeg)

</details>

すごい！！
LINEのリッチメニューから**練習**のためになる動画を見に行けて
足りなくなったボールやティーなどをオンラインで**購入**しに行けて
ゴルフ場の**予約**を取りにWebサイトに行けて
**天気**を簡単に出してくれる。

会話の内容はすべてゴルフに紐づけられる。
自然とゴルフのモチベーションが上がること間違いなし！

LINEの友達が
自分の好きなことに関連した
超便利なLINE Botだけになる日も近い。

## 3. LINE Bot作成の手順
* LINE Developersにログイン
* プロバイダーを作成
* リッチメニューを作成
* ChatGPTのAPIを利用
* チャネルシークレットやアクセストークンのコードへの組み込み

### 3-1. LINE Developersにログインする。
https://developers.line.biz/ja/

![スクリーンショット 2023-11-21 005916.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3554996/7d613629-84d2-40e3-7627-bee171fd63d3.png)

ここで新規にプロバイダーを作成する。


### 3-2. リッチメニューを作成する

https://developers.line.biz/ja/docs/messaging-api/using-rich-menus/

このWebサイトを参考にオリジナルのリッチメニューを作成することができる。
画像やイラストを用意しておくとよりオリジナリティーが出る。

### 3-3. ChatGPTのAPI利用

https://qiita.com/okinakamasayoshi/items/247c58342e5cf15c7bcd

超絶わかりやすく解説してくれている記事なので参考にして
ChatGPT/OpenAIのAPIを利用できるようにAPIキーを発行する。

### 3-4. コードに必要な情報を組み込む

プッシュメッセージでテキストやスタンプを送るには
```js
const messages = [{
        type: 'text',
        text: 'ナイスショット！\nいい調子だね！'
    }];
```
```js
const messages = [{
        "type": "sticker",
        "packageId": "1070",
        "stickerId": "17849"
      }];
```
コードのこれらの部分を変えることで可能だ。
スタンプの情報は以下のページに詳しく記載されている。

https://developers.line.biz/ja/docs/messaging-api/sticker-list/

ChatGTPの応答能力にプロンプトを加えることで
返答内容に味をつけることができる。
```js
const makeCompletion = async (userMessage) => {
  const prompt = {
      role: "system", 
      content: "ゴルフに関連させて答えてください" // プロンプトを入力
  };
```

今回は返答内容をすべてゴルフに関連するように指示をしてみた。
結果的には何を話しているかわからないような内容でもゴルフのことだと認識して
返事をしてくれるようになった。

まさにゴルフのためだけの
いや、私のためだけのLINE Botである。

### 4. まとめ
* LINE Botは大多数の人に情報発信をしていくツールとして便利である。
* テキスト形式の情報であれば簡単に発信ができる。
* 自分好みにカスタマイズができる。
* 欲しい分野の情報をすぐに受け取れる形の自分好みのLINE Botを個人利用するという選択肢もある。
* 必要な情報が多く煩雑な分野について情報を整理しておくことができる。
* ChatGPTの実装により指定した分野に関する対話をLINE上でパーソナリティのない相手とできる。
* 情報があふれて増加していく社会の中でこういったLINEBotはその情報に制限をかけてくれる。



### こんなわがままLINEBotの友達は私しかいない