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保存機能をつける