LINEbot 特定のメッセージの取得

LINEbot 特定のメッセージの取得

from google.cloud import firestore
import datetime

# Firestore クライアントの初期化
db = firestore.Client.from_service_account_json("serviceAccountKey.json")

def get_user_messages(user_id):
    # Firestore のコレクション名が "messages" であると仮定
    messages_ref = db.collection("messages")
    
    # user_id に一致するドキュメントをクエリで取得
    query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
    
    # メッセージを取得してリストに格納
    user_messages = []
    for doc in query.stream():
        user_messages.append(doc.to_dict())

    return user_messages

# 特定のユーザーIDを指定してメッセージを取得
user_id = ""
messages = get_user_messages(user_id)

for message in messages:
    print(f"メッセージ: {message['text']}, タイムスタンプ: {message['timestamp']}")

を実行したら

/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/base_collection.py:304: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  return query.where(field_path, op_string, value)
/Users/snowpool/aw10s/linebot/get_message.py:12: UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.
  query = messages_ref.where("user_id", "==", user_id).where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731082506.541246 3718460 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 162, in error_remapped_callable
    return _StreamingResponseIterator(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 88, in __init__
    self._stored_first_result = next(self._wrapped)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/grpc/_channel.py", line 543, in __next__
    return self._next()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/grpc/_channel.py", line 969, in _next
    raise self
grpc._channel._MultiThreadedRendezvous: <_MultiThreadedRendezvous of RPC that terminated with:
	status = StatusCode.FAILED_PRECONDITION
	details = "The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/voicelinebot/firestore/indexes?create_composite=

	debug_error_string = "UNKNOWN:Error received from peer ipv4:142.251.42.170:443 {grpc_message:"The query requires an index. You can create it here: https://console.firebase.google.com/v1/r/project/voicelinebot/firestore/indexes?create_composite=, grpc_status:9, created_time:"2024-11-09T01:15:06.942376+09:00"}"
>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 23, in <module>
    messages = get_user_messages(user_id)
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 16, in get_user_messages
    for doc in query.stream():
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/stream_generator.py", line 58, in __next__
    return self._generator.__next__()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/query.py", line 400, in _make_stream
    response_iterator, expected_prefix = self._get_stream_iterator(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/query.py", line 250, in _get_stream_iterator
    response_iterator = self._client._firestore_api.run_query(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/cloud/firestore_v1/services/firestore/client.py", line 1558, in run_query
    response = rpc(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/gapic_v1/method.py", line 113, in __call__
    return wrapped_func(*args, **kwargs)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/retry.py", line 349, in retry_wrapped_func
    return retry_target(
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/retry.py", line 191, in retry_target
    return target()
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/timeout.py", line 120, in func_with_timeout
    return func(*args, **kwargs)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/google/api_core/grpc_helpers.py", line 166, in error_remapped_callable
    raise exceptions.from_grpc_error(exc) from exc
google.api_core.exceptions.FailedPrecondition: 400 The query requires an index. You can create it here: 

となった

このエラーログをGPTへ貼り付け
解説と対処を日本語で表示

結果は

このエラーは、Firestoreで複合インデックスが必要なクエリを実行しようとしたために発生しています。
where(“user_id”, “==”, user_id).where(“timestamp”, “>=”, datetime.datetime.now() – datetime.timedelta(days=1))という複数条件のクエリを実行した際、
Firestoreは自動で複合インデックスを作成しません。そのため、手動でインデックスを作成する必要があります。

とのこと

Firestoreインデックス作成ページにアクセス

インデックス作成画面で、user_idとtimestampフィールドを基にしたインデックスを作成する

インデックスを追加をクリック

コレクションIDには「messages」と入力
(クエリで指定したコレクション名と一致させる必要があるため)

フィールドのパスに
user_id を入力し
Ascending のままでOK
意味は照準

同様に
time_stampも設定する

クエリのスコープで
コレクション
特定のコレクション パス内のクエリの場合

コレクション グループ
同じコレクション ID を持つすべてのコレクションを対象とするクエリ用

とあるのでGPTで調べる

「コレクション」スコープと「コレクション グループ」スコープのどちらが適しているかは、
アプリケーションの構造や必要なデータ取得の範囲によって異なります。

### 1. **コレクション スコープ**
– **対象**:特定のコレクション内のドキュメントを対象とします。
– **メリット**:クエリ範囲が狭いため、効率的で検索速度が速くなります。
– **適用例**:ユーザーごとに分けた特定のコレクションからメッセージを取得する場合などに適しています。
– **おすすめの利用場面**:
特定のコレクションのみからデータを取得する場合や、データ構造が単一階層で構成されている場合。

### 2. **コレクション グループ スコープ**
– **対象**:同じコレクションIDを持つすべてのサブコレクションに対してクエリを行います。
– **メリット**:複数の階層やドキュメントにまたがって一括検索ができるため、
   データの階層が深くても一度のクエリで対象をカバーできます。
– **適用例**:例えば、複数のユーザーごとに階層を作ってメッセージサブコレクションがある場合、
   すべてのユーザーの「messages」サブコレクションからまとめてメッセージを検索する場合に便利です。
– **おすすめの利用場面**:
データが階層構造で保存されている場合や、複数のコレクション階層にまたがって同じデータ構造を持つ場合。

### 選択のポイント
– **単一のコレクション内で特定ユーザーや特定の条件のデータを取得したい場合**は「コレクション」スコープが最適です。
– **すべてのユーザーのデータに対して同時に検索をかけたい場合**や、
  データが階層化されている場合は「コレクション グループ」スコープが便利です。

今回は特定のユーザーのメッセージにアクセスするので、**コレクション スコープ**で十分だと思われます。

とのこと

なので
コレクションを選択

これでインデックスを作成する

再度実行すると

The above exception was the direct cause of the following exception:

WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1731083489.912237 3734254 config.cc:230] gRPC experiments enabled: call_status_override_on_cancellation, event_engine_dns, event_engine_listener, http2_stats_fix, monitoring_experiment, pick_first_new, trace_record_callops, work_serializer_clears_time_cache

Firestoreの警告メッセージで指摘されているように、
whereメソッドの代わりに filter キーワードを使用して条件を指定すると警告が解消されます
とのこと

query = messages_ref \
    .where("user_id", "==", user_id) \
    .where("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
の部分を
query = messages_ref.filter("user_id", "==", user_id).filter("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))

に変更

しかし

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 25, in <module>
    messages = get_user_messages(user_id)
  File "/Users/snowpool/aw10s/linebot/get_message.py", line 13, in get_user_messages
    query = messages_ref.filter("user_id", "==", user_id).filter("timestamp", ">=", datetime.datetime.now() - datetime.timedelta(days=1))
AttributeError: 'CollectionReference' object has no attribute 'filter'

となる

AttributeError: ‘CollectionReference’ object has no attribute ‘filter’ というエラーは、
FirestoreのPython SDKには filter メソッドが存在しないために発生

where メソッドを使用して、複数の条件をチェーンで繋げる

しかし結果は変わらない

一度
main2.pyを実行し

音声メッセージを linebot へ送信

本来ならこれを取得できるはずだがDBに保存されていない

このため保存するコードを追加することにする

npm install firebase-admin

ってなっているけど
そもそもインストールがあってるか怪しいので調べる

npm install -g firebase-tools   

でインストールしてるので多分OK

npm install firebase-admin

を実行したら

added 158 packages in 20s 13 packages are looking for funding run npm fund for details npm notice npm notice New minor version of npm available! 10.7.0 -> 10.9.0 npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.9.0 npm notice To update run: npm install -g npm@10.9.0 npm notice

となったのでOK

次にindex.js を書き換える

const functions = require("firebase-functions");
const express = require("express");
const line = require("@line/bot-sdk");

const config = {
  channelAccessToken: functions.config().line.channel_access_token,
  channelSecret: functions.config().line.channel_secret,
};

const app = express();

app.post("/webhook", line.middleware(config), (req, res) => {
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  return client.replyMessage(event.replyToken, {
    type: "text",
    text: event.message.text,
  });
}

exports.webhook = functions.https.onRequest(app);

const functions = require("firebase-functions");
const express = require("express");
const line = require("@line/bot-sdk");
const admin = require("firebase-admin");

// Firebase Admin SDKの初期化
admin.initializeApp();
const db = admin.firestore();

const config = {
  channelAccessToken: functions.config().line.channel_access_token,
  channelSecret: functions.config().line.channel_secret,
};

const app = express();
const client = new line.Client(config);

// 特定のユーザーID
const targetUserId = "";  // 実際のLINEユーザーIDに置き換えてください

// Firestoreにメッセージを保存する関数
async function saveMessageToFirestore(userId, text) {
  const currentTimestamp = admin.firestore.FieldValue.serverTimestamp();

  // Firestoreにメッセージを保存
  await db.collection("messages").add({
    user_id: userId,        // LINEのユーザーID
    text: text,              // メッセージ本文
    timestamp: currentTimestamp,
    read: false,             // 既読状態
  });
}

// LINEメッセージのイベント処理
async function handleEvent(event) {
  if (event.type !== "message" || event.message.type !== "text") {
    return Promise.resolve(null);
  }

  const userId = event.source.userId;
  const messageText = event.message.text;

  // 特定のユーザーからのメッセージのみFirestoreに保存
  if (userId === targetUserId) {
    await saveMessageToFirestore(userId, messageText);
    console.log("メッセージがFirestoreに保存されました:", messageText);
  }

  // メッセージ返信
  return client.replyMessage(event.replyToken, {
    type: "text",
    text: messageText,
  });
}

app.post("/webhook", line.middleware(config), (req, res) => {
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});

// Firebase Functionsのエントリーポイント
exports.webhook = functions.https.onRequest(app);

へ変更

これで

firebase deploy --only functions --gen 1

とすると

error: unknown option '--gen'

   ╭────────────────────────────────────────────────────────────────────╮
   │                                                                    │
   │                 Update available 13.18.0 → 13.24.2                 │
   │           To update to the latest version using npm, run           │
   │                   npm install -g firebase-tools                    │
   │   For other CLI management options, visit the CLI documentation    │
   │         (https://firebase.google.com/docs/cli#update-cli)          │
   │                                                                    │
   │                                                                    │
   │                                                                    │
   ╰────────────────────────────────────────────────────────────────────╯

となる

npm install --save firebase-functions@latest

を実行

added 70 packages, and audited 229 packages in 3s

25 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

再度デプロイするので

firebase deploy --only functions     

を実行

✔  Deploy complete!

実行はできたけど

python main2.py                

を実行し

[2024-11-09 02:38:39.425] [ctranslate2] [thread 3780473] [warning] The compute type inferred from the saved model is float16, but the target device or backend do not support efficient float16 computation. The model weights have been automatically converted to use the float32 compute type instead.
stand by ready OK
recording...
finished
アロエベラはれ
stand by ready OK
recording...
finished
10秒間音声が入力されなかったため、ループを終了します。

入力された音声テキスト一覧:
アロエベラはれ

この結果を取得しようとしたけど
DBに格納されていない

index.jsのコード変更だけでは保存機能が動作しない?
ということでGPTで調べる

GPTによれば

はい、`index.js`のコード変更だけでは、Firestoreに保存機能が動作しない可能性があります。
`index.js`では主にLINEbotからのメッセージをFirestoreに保存する役割を果たしますが、
**`main2.py`の実行中に音声から認識されたテキストをFirestoreに保存するには、
`main2.py`内にFirestoreへの保存処理を追加する必要があります**。

以下が、`index.js`と`main2.py`の役割の違いです:

1. **`index.js`の役割**
– `index.js`は、LINEbotのWebhookとして機能し、LINEのユーザーからメッセージを受信した際に、そのメッセージをFirestoreに保存します。
– したがって、`index.js`はLINEからの入力メッセージに対して保存処理を行うものです。

2. **`main2.py`の役割**
– `main2.py`は、音声認識を行い、認識されたテキストをLINE NotifyやLINE Botに送信する役割を持っています。
– Firestoreに認識されたテキストを保存したい場合、`main2.py`内にFirestoreへの保存処理(`save_to_firestore`関数)を追加する必要があります。

### `main2.py`にFirestore保存機能を追加する理由

– `main2.py`が音声認識で生成したテキストは、LINEのメッセージとは異なるソースからのデータです。そのため、このテキストデータをFirestoreに保存するためには、`main2.py`内でFirestore保存処理を直接行う必要があります。

– `index.js`の変更は、LINEメッセージのみをFirestoreに保存する機能を提供するものなので、音声から生成されたテキストには影響を与えません。

したがって、**音声認識されたテキストをFirestoreに保存するには、`main2.py`内にFirestore保存機能を追加することが必須**です。

ということ
なのでwebhookの設定だけじゃだめ

次にmain2.pyを改造してmain3.pyとして
これに firestore保存機能をつける

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です